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:
# 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(),

View File

@ -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

View File

@ -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

View File

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

View File

@ -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 = '' }) => {
<div className={`relative ${className}`}>
<DropdownMenu
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">
<Image
src={getGravatarUrl(user?.email)}
src={selectedEstablishmentLogo ? `${BASE_URL}${selectedEstablishmentLogo}` : getGravatarUrl(user?.email)}
alt="Profile"
className="w-10 h-10 rounded-full object-cover shadow-md"
width={32}
height={32}
className="w-16 h-16 rounded-full object-cover shadow-md"
width={64}
height={64}
/>
{/* Bulle de statut de connexion au chat */}
<div
@ -116,7 +124,7 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
</div>
<div className="flex-1 min-w-0">
<div
className="font-bold text-left truncate max-w-full"
className="font-bold text-left truncate max-w-full text-sm"
title={user?.email}
>
{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}`
: ''}
</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>
<ChevronDown
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 (
<div className="w-64 bg-stone-50 border-r h-full border-gray-200">
<div className="border-b border-gray-200 ">
<ProfileSelector className="border-none" />
<ProfileSelector className="border-none h-24" />
</div>
<nav className="space-y-1 px-4 py-6">
{items.map((item) => (

View File

@ -50,6 +50,10 @@ export const EstablishmentProvider = ({ children }) => {
const storedApiDocuseal = sessionStorage.getItem('apiDocuseal');
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
const setSelectedEstablishmentId = (id) => {
@ -95,6 +99,11 @@ export const EstablishmentProvider = ({ children }) => {
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)
* @param {*} session
@ -114,6 +123,7 @@ export const EstablishmentProvider = ({ children }) => {
evaluation_frequency: role.establishment__evaluation_frequency,
total_capacity: role.establishment__total_capacity,
api_docuseal: role.establishment__api_docuseal,
logo: role.establishment__logo,
role_id: i,
role_type: role.role_type,
}));
@ -136,6 +146,9 @@ export const EstablishmentProvider = ({ children }) => {
setApiDocuseal(
userEstablishments[roleIndexDefault].api_docuseal
);
setSelectedEstablishmentLogo(
userEstablishments[roleIndexDefault].logo
);
setProfileRole(userEstablishments[roleIndexDefault].role_type);
}
if (endInitFunctionHandler) {
@ -156,6 +169,7 @@ export const EstablishmentProvider = ({ children }) => {
setSelectedEstablishmentEvaluationFrequencyState(null);
setSelectedEstablishmentTotalCapacityState(null);
setApiDocusealState(null);
setSelectedEstablishmentLogoState(null);
sessionStorage.clear();
};
@ -172,6 +186,8 @@ export const EstablishmentProvider = ({ children }) => {
setSelectedEstablishmentTotalCapacity,
apiDocuseal,
setApiDocuseal,
selectedEstablishmentLogo,
setSelectedEstablishmentLogo,
selectedRoleId,
setSelectedRoleId,
profileRole,

View File

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