From 6a0b90e98fbcc707756ae7fbbff921e480f2c695 Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sat, 31 May 2025 13:22:40 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Ajout=20du=20logo=20de=20l'=C3=A9cole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back-End/Auth/views.py | 21 ++++++++++++-- Back-End/Establishment/models.py | 11 +++++++ Back-End/Establishment/views.py | 20 ++++++++++--- Front-End/next.config.mjs | 5 ++++ Front-End/src/components/ProfileSelector.js | 29 ++++++++++++++----- Front-End/src/components/Sidebar.js | 2 +- Front-End/src/context/EstablishmentContext.js | 16 ++++++++++ Front-End/src/utils/rights.js | 6 ++-- 8 files changed, 91 insertions(+), 19 deletions(-) diff --git a/Back-End/Auth/views.py b/Back-End/Auth/views.py index b99e990..ccf9478 100644 --- a/Back-End/Auth/views.py +++ b/Back-End/Auth/views.py @@ -223,14 +223,29 @@ def makeToken(user): """ try: # 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 access_payload = { 'user_id': user.id, 'email': user.email, - 'roleIndexLoginDefault':user.roleIndexLoginDefault, - 'roles': list(roles), + 'roleIndexLoginDefault': user.roleIndexLoginDefault, + 'roles': roles, 'type': 'access', 'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'], 'iat': datetime.utcnow(), diff --git a/Back-End/Establishment/models.py b/Back-End/Establishment/models.py index 86d8a1d..27d6099 100644 --- a/Back-End/Establishment/models.py +++ b/Back-End/Establishment/models.py @@ -2,6 +2,12 @@ from django.db import models from django.contrib.postgres.fields import ArrayField 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): MATERNELLE = 1, _('Maternelle') PRIMAIRE = 2, _('Primaire') @@ -22,6 +28,11 @@ class Establishment(models.Model): is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=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): return self.name \ No newline at end of file diff --git a/Back-End/Establishment/views.py b/Back-End/Establishment/views.py index ab5eedf..0f028cb 100644 --- a/Back-End/Establishment/views.py +++ b/Back-End/Establishment/views.py @@ -1,7 +1,7 @@ from django.http.response import JsonResponse from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect 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 import status from .models import Establishment @@ -12,6 +12,8 @@ from django.db.models import Q from Auth.models import Profile, ProfileRole, Directeur from Settings.models import SMTPSettings import N3wtSchool.mailManager as mailer +import os +from N3wtSchool import settings @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') @@ -42,6 +44,8 @@ class EstablishmentListCreateView(APIView): @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') class EstablishmentDetailView(APIView): + parser_classes = [MultiPartParser, FormParser] + def get(self, request, id=None): try: 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) 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: establishment = Establishment.objects.get(id=id) except Establishment.DoesNotExist: 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(): 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) def delete(self, request, id): @@ -67,6 +76,7 @@ class EstablishmentDetailView(APIView): def create_establishment_with_directeur(establishment_data): # Extraction des sous-objets + # school_name = establishment_data.get("name") directeur_data = establishment_data.pop("directeur", None) smtp_settings_data = establishment_data.pop("smtp_settings", {}) @@ -91,6 +101,8 @@ def create_establishment_with_directeur(establishment_data): # Création de l'établissement establishment_serializer = EstablishmentSerializer(data=establishment_data) 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() # Création ou récupération du ProfileRole ADMIN pour ce profil et cet établissement diff --git a/Front-End/next.config.mjs b/Front-End/next.config.mjs index ffe8f35..095c420 100644 --- a/Front-End/next.config.mjs +++ b/Front-End/next.config.mjs @@ -18,6 +18,11 @@ const nextConfig = { protocol: 'https', hostname: 'www.gravatar.com', }, + { + protocol: 'http', + hostname: 'localhost', + port: '8080', + }, ], }, env: { diff --git a/Front-End/src/components/ProfileSelector.js b/Front-End/src/components/ProfileSelector.js index 487d611..9c470f2 100644 --- a/Front-End/src/components/ProfileSelector.js +++ b/Front-End/src/components/ProfileSelector.js @@ -9,6 +9,9 @@ import { usePopup } from '@/context/PopupContext'; import { getRightStr } from '@/utils/rights'; import { ChevronDown } from 'lucide-react'; // Import de l'icône import Image from 'next/image'; // Import du composant Image +import { + BASE_URL, +} from '@/utils/Url'; const ProfileSelector = ({ onRoleChange, className = '' }) => { const { @@ -20,6 +23,8 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => { user, setSelectedEstablishmentEvaluationFrequency, setSelectedEstablishmentTotalCapacity, + selectedEstablishmentLogo, + setSelectedEstablishmentLogo } = useEstablishment(); const { isConnected, connectionStatus } = useChatConnection(); const [dropdownOpen, setDropdownOpen] = useState(false); @@ -33,12 +38,15 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => { user.roles[roleId].establishment__evaluation_frequency; const establishmentTotalCapacity = user.roles[roleId].establishment__total_capacity; + const establishmentLogo = + user.roles[roleId].establishment__logo; setProfileRole(role); setSelectedEstablishmentId(establishmentId); setSelectedEstablishmentEvaluationFrequency( establishmentEvaluationFrequency ); setSelectedEstablishmentTotalCapacity(establishmentTotalCapacity); + setSelectedEstablishmentLogo(establishmentLogo); setSelectedRoleId(roleId); if (onRoleChange) { onRoleChange(roleId); @@ -99,14 +107,14 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
+
Profile {/* Bulle de statut de connexion au chat */}
{
{user?.email} @@ -125,11 +133,16 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => { className="text-sm text-gray-500 text-left truncate max-w-full" title={`${getRightStr(selectedEstablishment?.role_type) || ''}${selectedEstablishment?.name ? ', ' + selectedEstablishment.name : ''}`} > - {getRightStr(selectedEstablishment?.role_type) || ''} {selectedEstablishment?.name - ? `, ${selectedEstablishment.name}` + ? `${selectedEstablishment.name}` : ''}
+
+ {getRightStr(selectedEstablishment?.role_type) || ''} +
- +