feat: Ajout du logo de l'école

This commit is contained in:
N3WT DE COMPET
2025-05-31 13:22:40 +02:00
parent 8a71fa1830
commit 6a0b90e98f
8 changed files with 91 additions and 19 deletions

View File

@ -223,14 +223,29 @@ def makeToken(user):
""" """
try: try:
# Récupérer tous les rôles de l'utilisateur actifs # Récupérer tous les rôles de l'utilisateur actifs
roles = ProfileRole.objects.filter(profile=user, is_active=True).values('role_type', 'establishment__id', 'establishment__name', 'establishment__evaluation_frequency', 'establishment__total_capacity', 'establishment__api_docuseal') roles_qs = ProfileRole.objects.filter(profile=user, is_active=True).select_related('establishment')
roles = []
for role in roles_qs:
logo_url = ""
if role.establishment.logo:
# Construit l'URL complète pour le logo
logo_url = f"{role.establishment.logo.url}"
roles.append({
"role_type": role.role_type,
"establishment__id": role.establishment.id,
"establishment__name": role.establishment.name,
"establishment__evaluation_frequency": role.establishment.evaluation_frequency,
"establishment__total_capacity": role.establishment.total_capacity,
"establishment__api_docuseal": role.establishment.api_docuseal,
"establishment__logo": logo_url,
})
# Générer le JWT avec la bonne syntaxe datetime # Générer le JWT avec la bonne syntaxe datetime
access_payload = { access_payload = {
'user_id': user.id, 'user_id': user.id,
'email': user.email, 'email': user.email,
'roleIndexLoginDefault': user.roleIndexLoginDefault, 'roleIndexLoginDefault': user.roleIndexLoginDefault,
'roles': list(roles), 'roles': roles,
'type': 'access', 'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'], 'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(), 'iat': datetime.utcnow(),

View File

@ -2,6 +2,12 @@ from django.db import models
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import os
def registration_logo_upload_to(instance, filename):
ext = os.path.splitext(filename)[1]
return f"logos/school_{instance.pk}/logo{ext}"
class StructureType(models.IntegerChoices): class StructureType(models.IntegerChoices):
MATERNELLE = 1, _('Maternelle') MATERNELLE = 1, _('Maternelle')
PRIMAIRE = 2, _('Primaire') PRIMAIRE = 2, _('Primaire')
@ -22,6 +28,11 @@ class Establishment(models.Model):
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
api_docuseal = models.CharField(max_length=255, blank=True, null=True) api_docuseal = models.CharField(max_length=255, blank=True, null=True)
logo = models.FileField(
upload_to=registration_logo_upload_to,
null=True,
blank=True
)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -1,7 +1,7 @@
from django.http.response import JsonResponse from django.http.response import JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from rest_framework.parsers import JSONParser from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework import status from rest_framework import status
from .models import Establishment from .models import Establishment
@ -12,6 +12,8 @@ from django.db.models import Q
from Auth.models import Profile, ProfileRole, Directeur from Auth.models import Profile, ProfileRole, Directeur
from Settings.models import SMTPSettings from Settings.models import SMTPSettings
import N3wtSchool.mailManager as mailer import N3wtSchool.mailManager as mailer
import os
from N3wtSchool import settings
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
@ -42,6 +44,8 @@ class EstablishmentListCreateView(APIView):
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentDetailView(APIView): class EstablishmentDetailView(APIView):
parser_classes = [MultiPartParser, FormParser]
def get(self, request, id=None): def get(self, request, id=None):
try: try:
establishment = Establishment.objects.get(id=id) establishment = Establishment.objects.get(id=id)
@ -51,15 +55,20 @@ class EstablishmentDetailView(APIView):
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND) return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id): def put(self, request, id):
establishment_data = JSONParser().parse(request) """
Met à jour un établissement existant.
Accepte les données en multipart/form-data pour permettre l'upload de fichiers (ex : logo).
"""
try: try:
establishment = Establishment.objects.get(id=id) establishment = Establishment.objects.get(id=id)
except Establishment.DoesNotExist: except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND) return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
establishment_serializer = EstablishmentSerializer(establishment, data=establishment_data, partial=True)
# Utilise request.data pour supporter multipart/form-data (fichiers et champs classiques)
establishment_serializer = EstablishmentSerializer(establishment, data=request.data, partial=True)
if establishment_serializer.is_valid(): if establishment_serializer.is_valid():
establishment_serializer.save() establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False) return JsonResponse(establishment_serializer.data, safe=False, status=status.HTTP_200_OK)
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST) return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id): def delete(self, request, id):
@ -67,6 +76,7 @@ class EstablishmentDetailView(APIView):
def create_establishment_with_directeur(establishment_data): def create_establishment_with_directeur(establishment_data):
# Extraction des sous-objets # Extraction des sous-objets
# school_name = establishment_data.get("name")
directeur_data = establishment_data.pop("directeur", None) directeur_data = establishment_data.pop("directeur", None)
smtp_settings_data = establishment_data.pop("smtp_settings", {}) smtp_settings_data = establishment_data.pop("smtp_settings", {})
@ -91,6 +101,8 @@ def create_establishment_with_directeur(establishment_data):
# Création de l'établissement # Création de l'établissement
establishment_serializer = EstablishmentSerializer(data=establishment_data) establishment_serializer = EstablishmentSerializer(data=establishment_data)
establishment_serializer.is_valid(raise_exception=True) establishment_serializer.is_valid(raise_exception=True)
# base_dir = os.path.join(settings.MEDIA_ROOT, f"logo/school_{school_name}")
# os.makedirs(base_dir, exist_ok=True)
establishment = establishment_serializer.save() establishment = establishment_serializer.save()
# Création ou récupération du ProfileRole ADMIN pour ce profil et cet établissement # Création ou récupération du ProfileRole ADMIN pour ce profil et cet établissement

View File

@ -18,6 +18,11 @@ const nextConfig = {
protocol: 'https', protocol: 'https',
hostname: 'www.gravatar.com', hostname: 'www.gravatar.com',
}, },
{
protocol: 'http',
hostname: 'localhost',
port: '8080',
},
], ],
}, },
env: { env: {

View File

@ -9,6 +9,9 @@ import { usePopup } from '@/context/PopupContext';
import { getRightStr } from '@/utils/rights'; import { getRightStr } from '@/utils/rights';
import { ChevronDown } from 'lucide-react'; // Import de l'icône import { ChevronDown } from 'lucide-react'; // Import de l'icône
import Image from 'next/image'; // Import du composant Image import Image from 'next/image'; // Import du composant Image
import {
BASE_URL,
} from '@/utils/Url';
const ProfileSelector = ({ onRoleChange, className = '' }) => { const ProfileSelector = ({ onRoleChange, className = '' }) => {
const { const {
@ -20,6 +23,8 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
user, user,
setSelectedEstablishmentEvaluationFrequency, setSelectedEstablishmentEvaluationFrequency,
setSelectedEstablishmentTotalCapacity, setSelectedEstablishmentTotalCapacity,
selectedEstablishmentLogo,
setSelectedEstablishmentLogo
} = useEstablishment(); } = useEstablishment();
const { isConnected, connectionStatus } = useChatConnection(); const { isConnected, connectionStatus } = useChatConnection();
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
@ -33,12 +38,15 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
user.roles[roleId].establishment__evaluation_frequency; user.roles[roleId].establishment__evaluation_frequency;
const establishmentTotalCapacity = const establishmentTotalCapacity =
user.roles[roleId].establishment__total_capacity; user.roles[roleId].establishment__total_capacity;
const establishmentLogo =
user.roles[roleId].establishment__logo;
setProfileRole(role); setProfileRole(role);
setSelectedEstablishmentId(establishmentId); setSelectedEstablishmentId(establishmentId);
setSelectedEstablishmentEvaluationFrequency( setSelectedEstablishmentEvaluationFrequency(
establishmentEvaluationFrequency establishmentEvaluationFrequency
); );
setSelectedEstablishmentTotalCapacity(establishmentTotalCapacity); setSelectedEstablishmentTotalCapacity(establishmentTotalCapacity);
setSelectedEstablishmentLogo(establishmentLogo);
setSelectedRoleId(roleId); setSelectedRoleId(roleId);
if (onRoleChange) { if (onRoleChange) {
onRoleChange(roleId); onRoleChange(roleId);
@ -99,14 +107,14 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
<div className={`relative ${className}`}> <div className={`relative ${className}`}>
<DropdownMenu <DropdownMenu
buttonContent={ buttonContent={
<div className="h-16 flex items-center gap-2 cursor-pointer px-4 bg-white"> <div className="h-16 flex items-center gap-2 cursor-pointer px-4 bg-white h-24">
<div className="relative"> <div className="relative">
<Image <Image
src={getGravatarUrl(user?.email)} src={selectedEstablishmentLogo ? `${BASE_URL}${selectedEstablishmentLogo}` : getGravatarUrl(user?.email)}
alt="Profile" alt="Profile"
className="w-10 h-10 rounded-full object-cover shadow-md" className="w-16 h-16 rounded-full object-cover shadow-md"
width={32} width={64}
height={32} height={64}
/> />
{/* Bulle de statut de connexion au chat */} {/* Bulle de statut de connexion au chat */}
<div <div
@ -116,7 +124,7 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div <div
className="font-bold text-left truncate max-w-full" className="font-bold text-left truncate max-w-full text-sm"
title={user?.email} title={user?.email}
> >
{user?.email} {user?.email}
@ -125,11 +133,16 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
className="text-sm text-gray-500 text-left truncate max-w-full" className="text-sm text-gray-500 text-left truncate max-w-full"
title={`${getRightStr(selectedEstablishment?.role_type) || ''}${selectedEstablishment?.name ? ', ' + selectedEstablishment.name : ''}`} title={`${getRightStr(selectedEstablishment?.role_type) || ''}${selectedEstablishment?.name ? ', ' + selectedEstablishment.name : ''}`}
> >
{getRightStr(selectedEstablishment?.role_type) || ''}
{selectedEstablishment?.name {selectedEstablishment?.name
? `, ${selectedEstablishment.name}` ? `${selectedEstablishment.name}`
: ''} : ''}
</div> </div>
<div
className="italic text-sm text-gray-500 text-left truncate max-w-full"
title={`${getRightStr(selectedEstablishment?.role_type) || ''}${selectedEstablishment?.name ? ', ' + selectedEstablishment.name : ''}`}
>
{getRightStr(selectedEstablishment?.role_type) || ''}
</div>
</div> </div>
<ChevronDown <ChevronDown
className={`w-5 h-5 transition-transform duration-200 ${dropdownOpen ? 'rotate-180' : 'rotate-0'}`} className={`w-5 h-5 transition-transform duration-200 ${dropdownOpen ? 'rotate-180' : 'rotate-0'}`}

View File

@ -35,7 +35,7 @@ function Sidebar({ currentPage, items, onCloseMobile }) {
return ( return (
<div className="w-64 bg-stone-50 border-r h-full border-gray-200"> <div className="w-64 bg-stone-50 border-r h-full border-gray-200">
<div className="border-b border-gray-200 "> <div className="border-b border-gray-200 ">
<ProfileSelector className="border-none" /> <ProfileSelector className="border-none h-24" />
</div> </div>
<nav className="space-y-1 px-4 py-6"> <nav className="space-y-1 px-4 py-6">
{items.map((item) => ( {items.map((item) => (

View File

@ -50,6 +50,10 @@ export const EstablishmentProvider = ({ children }) => {
const storedApiDocuseal = sessionStorage.getItem('apiDocuseal'); const storedApiDocuseal = sessionStorage.getItem('apiDocuseal');
return storedApiDocuseal ? JSON.parse(storedApiDocuseal) : null; return storedApiDocuseal ? JSON.parse(storedApiDocuseal) : null;
}); });
const [selectedEstablishmentLogo, setSelectedEstablishmentLogoState] = useState(() => {
const storedLogo = sessionStorage.getItem('selectedEstablishmentLogo');
return storedLogo ? JSON.parse(storedLogo) : null;
});
// Sauvegarder dans sessionStorage à chaque mise à jour // Sauvegarder dans sessionStorage à chaque mise à jour
const setSelectedEstablishmentId = (id) => { const setSelectedEstablishmentId = (id) => {
@ -95,6 +99,11 @@ export const EstablishmentProvider = ({ children }) => {
sessionStorage.setItem('apiDocuseal', JSON.stringify(api)); sessionStorage.setItem('apiDocuseal', JSON.stringify(api));
}; };
const setSelectedEstablishmentLogo = (logo) => {
setSelectedEstablishmentLogoState(logo);
sessionStorage.setItem('selectedEstablishmentLogo', JSON.stringify(logo));
};
/** /**
* Fonction d'initialisation du contexte avec la session (appelée lors du login) * Fonction d'initialisation du contexte avec la session (appelée lors du login)
* @param {*} session * @param {*} session
@ -114,6 +123,7 @@ export const EstablishmentProvider = ({ children }) => {
evaluation_frequency: role.establishment__evaluation_frequency, evaluation_frequency: role.establishment__evaluation_frequency,
total_capacity: role.establishment__total_capacity, total_capacity: role.establishment__total_capacity,
api_docuseal: role.establishment__api_docuseal, api_docuseal: role.establishment__api_docuseal,
logo: role.establishment__logo,
role_id: i, role_id: i,
role_type: role.role_type, role_type: role.role_type,
})); }));
@ -136,6 +146,9 @@ export const EstablishmentProvider = ({ children }) => {
setApiDocuseal( setApiDocuseal(
userEstablishments[roleIndexDefault].api_docuseal userEstablishments[roleIndexDefault].api_docuseal
); );
setSelectedEstablishmentLogo(
userEstablishments[roleIndexDefault].logo
);
setProfileRole(userEstablishments[roleIndexDefault].role_type); setProfileRole(userEstablishments[roleIndexDefault].role_type);
} }
if (endInitFunctionHandler) { if (endInitFunctionHandler) {
@ -156,6 +169,7 @@ export const EstablishmentProvider = ({ children }) => {
setSelectedEstablishmentEvaluationFrequencyState(null); setSelectedEstablishmentEvaluationFrequencyState(null);
setSelectedEstablishmentTotalCapacityState(null); setSelectedEstablishmentTotalCapacityState(null);
setApiDocusealState(null); setApiDocusealState(null);
setSelectedEstablishmentLogoState(null);
sessionStorage.clear(); sessionStorage.clear();
}; };
@ -172,6 +186,8 @@ export const EstablishmentProvider = ({ children }) => {
setSelectedEstablishmentTotalCapacity, setSelectedEstablishmentTotalCapacity,
apiDocuseal, apiDocuseal,
setApiDocuseal, setApiDocuseal,
selectedEstablishmentLogo,
setSelectedEstablishmentLogo,
selectedRoleId, selectedRoleId,
setSelectedRoleId, setSelectedRoleId,
profileRole, profileRole,

View File

@ -6,11 +6,11 @@ export const RIGHTS = {
export function getRightStr(right) { export function getRightStr(right) {
if (right === RIGHTS.ADMIN) { if (right === RIGHTS.ADMIN) {
return 'ADMINISTRATEUR'; return 'Administrateur';
} else if (right === RIGHTS.TEACHER) { } else if (right === RIGHTS.TEACHER) {
return 'PROFESSEUR'; return 'Professeur';
} else if (right === RIGHTS.PARENT) { } else if (right === RIGHTS.PARENT) {
return 'PARENT'; return 'Parent';
} else { } else {
return 'NON DEFINI'; return 'NON DEFINI';
} }