mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Ajout du logo de l'école
This commit is contained in:
@ -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(),
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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'}`}
|
||||||
|
|||||||
@ -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) => (
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user