From cfa65daa19d85966fd7a80a2803d59a19ca1e1f7 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sun, 10 Dec 2023 17:09:56 +0100 Subject: [PATCH 1/8] implemented very basic login, logout and profile pages --- Palto/Palto/api/urls.py | 2 + Palto/Palto/api/v1/urls.py | 3 +- Palto/Palto/forms.py | 6 ++ Palto/Palto/static/Palto/favicon.svg | 1 + Palto/Palto/templates/Palto/base.html | 20 ++++++ Palto/Palto/templates/Palto/login.html | 9 +++ Palto/Palto/templates/Palto/navigation.html | 3 + Palto/Palto/templates/Palto/profile.html | 9 +++ Palto/Palto/urls.py | 21 +++++++ Palto/Palto/views.py | 70 ++++++++++++++++++++- Palto/urls.py | 7 ++- 11 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 Palto/Palto/forms.py create mode 100644 Palto/Palto/static/Palto/favicon.svg create mode 100644 Palto/Palto/templates/Palto/base.html create mode 100644 Palto/Palto/templates/Palto/login.html create mode 100644 Palto/Palto/templates/Palto/navigation.html create mode 100644 Palto/Palto/templates/Palto/profile.html create mode 100644 Palto/Palto/urls.py diff --git a/Palto/Palto/api/urls.py b/Palto/Palto/api/urls.py index 23f5062..ff33307 100644 --- a/Palto/Palto/api/urls.py +++ b/Palto/Palto/api/urls.py @@ -9,6 +9,8 @@ from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView import Palto.Palto.api.v1.urls as v1_urls +app_name = "PaltoAPI" + urlpatterns = [ # Authentification (JWT) path('auth/jwt/token/', TokenObtainPairView.as_view(), name='token'), diff --git a/Palto/Palto/api/v1/urls.py b/Palto/Palto/api/v1/urls.py index 221ea5f..2c54563 100644 --- a/Palto/Palto/api/v1/urls.py +++ b/Palto/Palto/api/v1/urls.py @@ -9,6 +9,8 @@ from rest_framework import routers from . import views +app_name = "PaltoAPIv1" + router = routers.DefaultRouter() router.register(r'users', views.UserViewSet, basename="User") @@ -21,5 +23,4 @@ router.register(r'attendances', views.AttendanceViewSet, basename="Attendance") router.register(r'absences', views.AbsenceViewSet, basename="Absence") router.register(r'absence_attachments', views.AbsenceAttachmentViewSet, basename="AbsenceAttachment") - urlpatterns = router.urls diff --git a/Palto/Palto/forms.py b/Palto/Palto/forms.py new file mode 100644 index 0000000..98c65bc --- /dev/null +++ b/Palto/Palto/forms.py @@ -0,0 +1,6 @@ +from django import forms + + +class LoginForm(forms.Form): + username = forms.CharField() + password = forms.CharField(widget=forms.PasswordInput) diff --git a/Palto/Palto/static/Palto/favicon.svg b/Palto/Palto/static/Palto/favicon.svg new file mode 100644 index 0000000..212b8b1 --- /dev/null +++ b/Palto/Palto/static/Palto/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Palto/Palto/templates/Palto/base.html b/Palto/Palto/templates/Palto/base.html new file mode 100644 index 0000000..f6d2f27 --- /dev/null +++ b/Palto/Palto/templates/Palto/base.html @@ -0,0 +1,20 @@ +{% load static %} + + + + + + + + + {% block title %}Palto{% endblock %} + + + {# navigation #} + {% include "Palto/navigation.html" %} + + {# body #} + {% block body %} + {% endblock %} + + diff --git a/Palto/Palto/templates/Palto/login.html b/Palto/Palto/templates/Palto/login.html new file mode 100644 index 0000000..8672a95 --- /dev/null +++ b/Palto/Palto/templates/Palto/login.html @@ -0,0 +1,9 @@ +{% extends "Palto/base.html" %} + +{% block body %} +
+ {% csrf_token %} + {{ form_login }} + +
+{% endblock %} \ No newline at end of file diff --git a/Palto/Palto/templates/Palto/navigation.html b/Palto/Palto/templates/Palto/navigation.html new file mode 100644 index 0000000..bb4c86f --- /dev/null +++ b/Palto/Palto/templates/Palto/navigation.html @@ -0,0 +1,3 @@ + diff --git a/Palto/Palto/templates/Palto/profile.html b/Palto/Palto/templates/Palto/profile.html new file mode 100644 index 0000000..177ba14 --- /dev/null +++ b/Palto/Palto/templates/Palto/profile.html @@ -0,0 +1,9 @@ +{% extends "Palto/base.html" %} + +{% block body %} +
+ {{ profile.username }} + {{ profile.email }} + {% if profile.is_superuser %}Administrator{% endif %} +
+{% endblock %} diff --git a/Palto/Palto/urls.py b/Palto/Palto/urls.py new file mode 100644 index 0000000..19c0b30 --- /dev/null +++ b/Palto/Palto/urls.py @@ -0,0 +1,21 @@ +""" +Urls for the Palto project's API. + +This file list all the urls for the Palto API. +""" + +from django.urls import path +from Palto.Palto import views + +app_name = "Palto" + +urlpatterns = [ + # Base + path("", views.homepage_view, name="homepage"), + + # User + path("login/", views.login_view, name="login"), + path("logout/", views.logout_view, name="logout"), + path("profile/", views.profile_view, name="profile"), + path("profile//", views.profile_view, name="profile"), +] diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index 49785f0..535aabc 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -3,7 +3,75 @@ Views for the Palto project. A view is what control the content of a page, prepare the correct data, react to a form, render the correct template. """ +import uuid +from django.contrib.auth import login, authenticate, logout +from django.contrib.auth.decorators import login_required +from django.http import HttpRequest, HttpResponse +from django.shortcuts import render, get_object_or_404, redirect + +from Palto.Palto.forms import LoginForm +from Palto.Palto.models import User -from django.shortcuts import render # Create your views here. +def homepage_view(request: HttpRequest): + # TODO: homepage + return HttpResponse("Hello there.") + + +def login_view(request: HttpRequest): + # create a login form + form_login = LoginForm(request.POST) + + if form_login.is_valid(): + # try to authenticate this user with the credentials + user = authenticate( + username=form_login.cleaned_data["username"], + password=form_login.cleaned_data["password"] + ) + + if user is not None: + # if the user was authenticated, log the user in. + login(request, user) + # redirect him to the main page + return redirect("Palto:homepage") + + else: + # otherwise the credentials were invalid. + form_login.add_error(field=None, error="Invalid credentials.") + + # return the page + return render( + request, + "Palto/login.html", + context=dict( + form_login=form_login + ) + ) + + +@login_required +def logout_view(request: HttpRequest): + # disconnect the user from the website + logout(request) + # redirect him to the main page + return redirect("Palto:homepage") + + +@login_required +def profile_view(request: HttpRequest, profile_id: uuid.UUID = None): + if profile_id is None: + # if the profile id is not given, redirect to the page of the current user. + return redirect("Palto:profile", request.user.id) + + # get the corresponding user from its id. + profile = get_object_or_404(User, id=profile_id) + + # render the page + return render( + request, + "Palto/profile.html", + context=dict( + profile=profile + ) + ) diff --git a/Palto/urls.py b/Palto/urls.py index 463604a..0697362 100644 --- a/Palto/urls.py +++ b/Palto/urls.py @@ -19,16 +19,17 @@ from django.contrib import admin from django.urls import path, re_path, include from django.views.static import serve -from Palto.Palto.api import urls as api_urls from Palto import settings +from Palto.Palto import urls as palto_views_urls +from Palto.Palto.api import urls as palto_api_urls urlpatterns = [ # Application - # ... + path('', include(palto_views_urls)), # API - path('api/', include(api_urls)), # Api REST + path('api/', include(palto_api_urls)), # Api REST # Debug path('admin/', admin.site.urls), # Admin page From 5f2aaf1a2ed35bd2d310859bc12c32f5a5b8f4d7 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Mon, 11 Dec 2023 19:29:02 +0100 Subject: [PATCH 2/8] implemented basic teaching session pages --- Palto/Palto/admin.py | 6 +- Palto/Palto/migrations/0001_initial.py | 166 ++++++++++++++++++ Palto/Palto/models.py | 3 + Palto/Palto/templates/Palto/homepage.html | 5 + Palto/Palto/templates/Palto/navigation.html | 10 +- .../templates/Palto/teaching_session.html | 27 +++ .../Palto/teaching_session_list.html | 36 ++++ Palto/Palto/urls.py | 4 +- Palto/Palto/views.py | 61 ++++++- 9 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 Palto/Palto/migrations/0001_initial.py create mode 100644 Palto/Palto/templates/Palto/homepage.html create mode 100644 Palto/Palto/templates/Palto/teaching_session.html create mode 100644 Palto/Palto/templates/Palto/teaching_session_list.html diff --git a/Palto/Palto/admin.py b/Palto/Palto/admin.py index 388aed0..86277a2 100644 --- a/Palto/Palto/admin.py +++ b/Palto/Palto/admin.py @@ -61,9 +61,9 @@ class AdminAttendance(admin.ModelAdmin): @admin.register(models.Absence) class AdminAbsence(admin.ModelAdmin): - list_display = ("id", "message", "student", "department", "start", "end") - search_fields = ("id", "message", "student", "department", "start", "end") - list_filter = ("department", "start", "end") + list_display = ("id", "message", "student", "start", "end") + search_fields = ("id", "message", "student", "start", "end") + list_filter = ("start", "end") @admin.register(models.AbsenceAttachment) diff --git a/Palto/Palto/migrations/0001_initial.py b/Palto/Palto/migrations/0001_initial.py new file mode 100644 index 0000000..56a14d7 --- /dev/null +++ b/Palto/Palto/migrations/0001_initial.py @@ -0,0 +1,166 @@ +# Generated by Django 4.2.7 on 2023-12-10 11:31 + +import Palto.Palto.models +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Absence', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('message', models.TextField()), + ('start', models.DateTimeField()), + ('end', models.DateTimeField()), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='Department', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64, unique=True)), + ('email', models.EmailField(max_length=254)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='StudentGroup', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_groups', to='Palto.department')), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='User', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='TeachingUnit', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_units', to='Palto.department')), + ('managers', models.ManyToManyField(blank=True, related_name='managing_units', to=settings.AUTH_USER_MODEL)), + ('student_groups', models.ManyToManyField(blank=True, related_name='studying_units', to='Palto.studentgroup')), + ('teachers', models.ManyToManyField(blank=True, related_name='teaching_units', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='TeachingSession', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('start', models.DateTimeField()), + ('duration', models.DurationField()), + ('note', models.TextField(blank=True)), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_sessions', to='Palto.studentgroup')), + ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_sessions', to=settings.AUTH_USER_MODEL)), + ('unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to='Palto.teachingunit')), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='studentgroup', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owning_groups', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='studentgroup', + name='students', + field=models.ManyToManyField(blank=True, related_name='student_groups', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='StudentCard', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('uid', models.BinaryField(max_length=7)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_cards', to='Palto.department')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_cards', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='department', + name='managers', + field=models.ManyToManyField(blank=True, related_name='managing_departments', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='department', + name='students', + field=models.ManyToManyField(blank=True, related_name='studying_departments', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='department', + name='teachers', + field=models.ManyToManyField(blank=True, related_name='teaching_departments', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='Attendance', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('date', models.DateTimeField()), + ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='Palto.teachingsession')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attended_sessions', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='AbsenceAttachment', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('content', models.FileField(upload_to='absence/attachment/')), + ('absence', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='Palto.absence')), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='absence', + name='department', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='Palto.department'), + ), + migrations.AddField( + model_name='absence', + name='student', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/Palto/Palto/models.py b/Palto/Palto/models.py index ac7f3d2..049e80b 100644 --- a/Palto/Palto/models.py +++ b/Palto/Palto/models.py @@ -63,6 +63,9 @@ class User(AbstractUser, ModelPermissionHelper): def __repr__(self): return f"<{self.__class__.__name__} id={str(self.id)[:8]} username={self.username!r}>" + def __str__(self): + return f"{self.first_name} {self.last_name.upper()}" + @staticmethod def multiple_related_departments(users: Iterable["User"]) -> QuerySet["Department"]: """ diff --git a/Palto/Palto/templates/Palto/homepage.html b/Palto/Palto/templates/Palto/homepage.html new file mode 100644 index 0000000..bade8c3 --- /dev/null +++ b/Palto/Palto/templates/Palto/homepage.html @@ -0,0 +1,5 @@ +{% extends "Palto/base.html" %} + +{% block body %} + Hello there. +{% endblock %} diff --git a/Palto/Palto/templates/Palto/navigation.html b/Palto/Palto/templates/Palto/navigation.html index bb4c86f..074a3bf 100644 --- a/Palto/Palto/templates/Palto/navigation.html +++ b/Palto/Palto/templates/Palto/navigation.html @@ -1,3 +1,11 @@ +{% load static %} + diff --git a/Palto/Palto/templates/Palto/teaching_session.html b/Palto/Palto/templates/Palto/teaching_session.html new file mode 100644 index 0000000..5a6c99c --- /dev/null +++ b/Palto/Palto/templates/Palto/teaching_session.html @@ -0,0 +1,27 @@ +{% extends "Palto/base.html" %} + +{% block body %} + {# table of all the sessions #} + + + + + + + + + + + + + + + + + + + + + +
IdentifiantDébutDuréeUnité d'EnseignementEnseignantGroupe
{{ session.id }}{{ session.start }}{{ session.duration }}{{ session.unit }}{{ session.teacher }}{{ session.group }}
+{% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session_list.html b/Palto/Palto/templates/Palto/teaching_session_list.html new file mode 100644 index 0000000..f9ecc3e --- /dev/null +++ b/Palto/Palto/templates/Palto/teaching_session_list.html @@ -0,0 +1,36 @@ +{% extends "Palto/base.html" %} + +{% block body %} + {# table of all the sessions #} + + + + + + + + + + {% for session in sessions %} + + + + + + {% endfor %} + +
IdentifiantDébutDurée
{{ session.id }}{{ session.start }}{{ session.duration }}
+ + {# page navigator #} +
+ {% if sessions.has_previous %} + Previous + {% endif %} + + {{ sessions.number }} + + {% if sessions.has_next %} + Next + {% endif %} +
+{% endblock %} diff --git a/Palto/Palto/urls.py b/Palto/Palto/urls.py index 19c0b30..357120f 100644 --- a/Palto/Palto/urls.py +++ b/Palto/Palto/urls.py @@ -16,6 +16,8 @@ urlpatterns = [ # User path("login/", views.login_view, name="login"), path("logout/", views.logout_view, name="logout"), - path("profile/", views.profile_view, name="profile"), + path("profile/", views.profile_view, name="my_profile"), path("profile//", views.profile_view, name="profile"), + path("teaching_sessions/", views.teaching_session_list_view, name="teaching_session_list"), + path("teaching_sessions//", views.teaching_session_view, name="teaching_session"), ] diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index 535aabc..b9ac81f 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -6,20 +6,24 @@ A view is what control the content of a page, prepare the correct data, react to import uuid from django.contrib.auth import login, authenticate, logout from django.contrib.auth.decorators import login_required -from django.http import HttpRequest, HttpResponse +from django.core.handlers.wsgi import WSGIRequest +from django.core.paginator import Paginator +from django.http import HttpResponseForbidden from django.shortcuts import render, get_object_or_404, redirect +from Palto.Palto import models from Palto.Palto.forms import LoginForm -from Palto.Palto.models import User + + +ELEMENT_PER_PAGE: int = 30 # Create your views here. -def homepage_view(request: HttpRequest): - # TODO: homepage - return HttpResponse("Hello there.") +def homepage_view(request: WSGIRequest): + return render(request, "Palto/homepage.html") -def login_view(request: HttpRequest): +def login_view(request: WSGIRequest): # create a login form form_login = LoginForm(request.POST) @@ -51,7 +55,7 @@ def login_view(request: HttpRequest): @login_required -def logout_view(request: HttpRequest): +def logout_view(request: WSGIRequest): # disconnect the user from the website logout(request) # redirect him to the main page @@ -59,13 +63,13 @@ def logout_view(request: HttpRequest): @login_required -def profile_view(request: HttpRequest, profile_id: uuid.UUID = None): +def profile_view(request: WSGIRequest, profile_id: uuid.UUID = None): if profile_id is None: # if the profile id is not given, redirect to the page of the current user. return redirect("Palto:profile", request.user.id) # get the corresponding user from its id. - profile = get_object_or_404(User, id=profile_id) + profile = get_object_or_404(models.User, id=profile_id) # render the page return render( @@ -75,3 +79,42 @@ def profile_view(request: HttpRequest, profile_id: uuid.UUID = None): profile=profile ) ) + + +@login_required +def teaching_session_list_view(request: WSGIRequest): + # get all the sessions that the user can see, sorted by starting date + raw_sessions = models.TeachingSession.all_visible_by_user(request.user).order_by("start") + # paginate them to avoid having too many elements at the same time + paginator = Paginator(raw_sessions, ELEMENT_PER_PAGE) + + # get only the session for the requested page + page = request.GET.get("page", 0) + sessions = paginator.get_page(page) + + # render the page + return render( + request, + "Palto/teaching_session_list.html", + context=dict( + sessions=sessions + ) + ) + + +@login_required +def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): + session = get_object_or_404(models.TeachingSession, id=session_id) + + if session not in models.TeachingSession.all_visible_by_user(request.user): + # TODO: syntaxic sugar session.visible_by_user(request.user) + return HttpResponseForbidden() + + # render the page + return render( + request, + "Palto/teaching_session.html", + context=dict( + session=session + ) + ) \ No newline at end of file From 82591b27a2f5d4138133dc5f57802bf732778cb3 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Mon, 11 Dec 2023 19:30:39 +0100 Subject: [PATCH 3/8] removed .idea files --- .idea/.gitignore | 8 ----- .idea/Palto-Server.iml | 29 ------------------- .../inspectionProfiles/profiles_settings.xml | 6 ---- .idea/misc.xml | 7 ----- .idea/modules.xml | 8 ----- .idea/vcs.xml | 6 ---- 6 files changed, 64 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/Palto-Server.iml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/Palto-Server.iml b/.idea/Palto-Server.iml deleted file mode 100644 index e92e3f9..0000000 --- a/.idea/Palto-Server.iml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7226c94..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 37c0c2e..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 784d4d4001fdbbbcfa8ddfdc92a4ddbe45742325 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Wed, 13 Dec 2023 23:12:51 +0100 Subject: [PATCH 4/8] detailled profile, session and session list pages --- Palto/Palto/models.py | 96 +++++++++++++------ Palto/Palto/templates/Palto/profile.html | 65 +++++++++++++ .../templates/Palto/teaching_session.html | 69 ++++++++++--- .../Palto/teaching_session_list.html | 15 ++- Palto/Palto/templatetags/__init__.py | 0 Palto/Palto/templatetags/dict_tags.py | 6 ++ Palto/Palto/utils.py | 15 +++ Palto/Palto/views.py | 40 +++++++- 8 files changed, 252 insertions(+), 54 deletions(-) create mode 100644 Palto/Palto/templatetags/__init__.py create mode 100644 Palto/Palto/templatetags/dict_tags.py create mode 100644 Palto/Palto/utils.py diff --git a/Palto/Palto/models.py b/Palto/Palto/models.py index 049e80b..761ef10 100644 --- a/Palto/Palto/models.py +++ b/Palto/Palto/models.py @@ -61,11 +61,15 @@ class User(AbstractUser, ModelPermissionHelper): id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36) def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} username={self.username!r}>" + return f"<{self.__class__.__name__} id={self.short_id} username={self.username!r}>" def __str__(self): return f"{self.first_name} {self.last_name.upper()}" + @property + def short_id(self) -> str: + return str(self.id)[:8] + @staticmethod def multiple_related_departments(users: Iterable["User"]) -> QuerySet["Department"]: """ @@ -141,11 +145,15 @@ class Department(models.Model, ModelPermissionHelper): students = models.ManyToManyField(to=User, blank=True, related_name="studying_departments") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>" + return f"<{self.__class__.__name__} id={self.short_id} name={self.name!r}>" def __str__(self): return self.name + @property + def short_id(self) -> str: + return str(self.id)[:8] + @staticmethod def multiple_related_users(departments: Iterable["Department"]) -> QuerySet["User"]: """ @@ -212,22 +220,26 @@ class StudentGroup(models.Model, ModelPermissionHelper): students = models.ManyToManyField(to=User, blank=True, related_name="student_groups") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>" + return f"<{self.__class__.__name__} id={self.short_id} name={self.name!r}>" def __str__(self): return self.name + @property + def short_id(self) -> str: + return str(self.id)[:8] + # validators def clean(self): super().clean() # owner check - if self.department not in self.owner.teaching_departments: + if self.department not in self.owner.teaching_departments.all(): raise ValidationError("The owner is not related to the department.") # students check - if not all(self.department in student.studying_departments for student in self.students.all()): + if not all(self.department in student.studying_departments.all() for student in self.students.all()): raise ValidationError("A student is not related to the department.") # permissions @@ -254,9 +266,9 @@ class StudentGroup(models.Model, ModelPermissionHelper): return { # the user can only interact with a related departments - "department": lambda data: user.managing_departments | user.teaching_departments, + "department": lambda data: (user.managing_departments | user.teaching_departments).all(), # the owner must be a teacher or a manager of this department - "owner": lambda data: data["department"].managers | data["department"].teachers, + "owner": lambda data: (data["department"].managers | data["department"].teachers).all(), } @classmethod @@ -314,26 +326,30 @@ class TeachingUnit(models.Model, ModelPermissionHelper): student_groups = models.ManyToManyField(to=StudentGroup, blank=True, related_name="studying_units") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>" + return f"<{self.__class__.__name__} id={self.short_id} name={self.name!r}>" def __str__(self): return self.name + @property + def short_id(self) -> str: + return str(self.id)[:8] + # validations def clean(self): super().clean() # managers check - if not all(self.department in manager.managing_departments for manager in self.managers.all()): + if not all(self.department in manager.managing_departments.all() for manager in self.managers.all()): raise ValidationError("A manager is not related to the department.") # teachers check - if not all(self.department in teacher.teaching_departments for teacher in self.teachers.all()): + if not all(self.department in teacher.teaching_departments.all() for teacher in self.teachers.all()): raise ValidationError("A teacher is not related to the department.") # student groups check - if not all(self.department in student_group.department for student_group in self.student_groups.all()): + if not all(self.department in student_group.department.all() for student_group in self.student_groups.all()): raise ValidationError("A student group is not related to the department.") # permissions @@ -356,7 +372,7 @@ class TeachingUnit(models.Model, ModelPermissionHelper): return { # a user can only interact with a related departments - "department": lambda data: user.managing_departments | user.teaching_departments + "department": lambda data: (user.managing_departments | user.teaching_departments).all() } @classmethod @@ -406,7 +422,11 @@ class StudentCard(models.Model, ModelPermissionHelper): owner: User = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="student_cards") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} owner={self.owner.username!r}>" + return f"<{self.__class__.__name__} id={self.short_id} owner={self.owner.username!r}>" + + @property + def short_id(self) -> str: + return str(self.id)[:8] # validations @@ -414,7 +434,7 @@ class StudentCard(models.Model, ModelPermissionHelper): super().clean() # owner check - if self.department not in self.owner.studying_departments: + if self.department not in self.owner.studying_departments.all(): raise ValidationError("The student is not related to the department.") # permissions @@ -436,7 +456,7 @@ class StudentCard(models.Model, ModelPermissionHelper): return { # a user can only interact with a related departments - "department": lambda field, data: field in user.managing_departments, + "department": lambda field, data: field in user.managing_departments.all(), } @classmethod @@ -488,11 +508,15 @@ class TeachingSession(models.Model, ModelPermissionHelper): teacher = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="teaching_sessions") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} unit={self.unit.name!r} start={self.start}>" + return f"<{self.__class__.__name__} id={self.short_id} unit={self.unit.name!r} start={self.start}>" def __str__(self): return f"{self.unit.name} ({self.start})" + @property + def short_id(self) -> str: + return str(self.id)[:8] + @property def end(self) -> datetime: return self.start + self.duration @@ -502,12 +526,12 @@ class TeachingSession(models.Model, ModelPermissionHelper): def clean(self): super().clean() - # group check - if self.unit.department not in self.group.department: + # department check + if self.unit.department != self.group.department: raise ValidationError("The group is not related to the unit department.") # teacher check - if self.unit not in self.teacher.teaching_units: + if self.unit not in self.teacher.teaching_units.all(): raise ValidationError("The teacher is not related to the unit.") # permissions @@ -545,7 +569,7 @@ class TeachingSession(models.Model, ModelPermissionHelper): user.teaching_units | # all the units of the department the user is managing TeachingUnit.objects.filter(pk__in=user.managing_departments.values("teaching_units")) - ) + ).all() } @classmethod @@ -609,19 +633,23 @@ class Attendance(models.Model, ModelPermissionHelper): def __repr__(self): return ( f"<{self.__class__.__name__} " - f"id={str(self.id)[:8]} " + f"id={self.short_id} " f"student={self.student.username} " - f"session={str(self.session.id)[:8]}" + f"session={self.session.short_id}" f">" ) + @property + def short_id(self) -> str: + return str(self.id)[:8] + # validations def clean(self): super().clean() # student check - if self.student not in self.session.group.students: + if self.student not in self.session.group.students.all(): raise ValidationError("The student is not related to the student group.") # permissions @@ -662,7 +690,7 @@ class Attendance(models.Model, ModelPermissionHelper): pk__in=user.managing_departments.values("teaching_units") ).values("sessions") ) - ) + ).all() } @classmethod @@ -721,7 +749,7 @@ class Absence(models.Model, ModelPermissionHelper): def __repr__(self): return ( f"<{self.__class__.__name__} " - f"id={str(self.id)[:8]} " + f"id={self.short_id} " f"department={self.department} " f"student={self.student.username} " f"start={self.start} " @@ -730,7 +758,11 @@ class Absence(models.Model, ModelPermissionHelper): ) def __str__(self): - return f"[{str(self.id)[:8]}] {self.student}" + return f"[{self.short_id}] {self.student}" + + @property + def short_id(self) -> str: + return str(self.id)[:8] # validations @@ -738,7 +770,7 @@ class Absence(models.Model, ModelPermissionHelper): super().clean() # student check - if self.department not in self.student.studying_departments: + if self.department not in self.student.studying_departments.all(): raise ValidationError("The student is not related to the department.") # properties @@ -775,7 +807,7 @@ class Absence(models.Model, ModelPermissionHelper): return { # all the departments the user is studying in - "department": lambda data: user.studying_departments, + "department": lambda data: user.studying_departments.all(), } @classmethod @@ -831,7 +863,11 @@ class AbsenceAttachment(models.Model, ModelPermissionHelper): absence = models.ForeignKey(to=Absence, on_delete=models.CASCADE, related_name="attachments") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} content={self.content!r}>" + return f"<{self.__class__.__name__} id={self.short_id} content={self.content!r}>" + + @property + def short_id(self) -> str: + return str(self.id)[:8] # permissions @@ -853,7 +889,7 @@ class AbsenceAttachment(models.Model, ModelPermissionHelper): return { # all the departments the user is studying in - "absence": lambda data: user.absences, + "absence": lambda data: user.absences.all(), } @classmethod diff --git a/Palto/Palto/templates/Palto/profile.html b/Palto/Palto/templates/Palto/profile.html index 177ba14..6ed7d5f 100644 --- a/Palto/Palto/templates/Palto/profile.html +++ b/Palto/Palto/templates/Palto/profile.html @@ -1,9 +1,74 @@ {% extends "Palto/base.html" %} +{% load dict_tags %} {% block body %}
{{ profile.username }} {{ profile.email }} {% if profile.is_superuser %}Administrator{% endif %} + + {# user related departments table #} + + {% for department, profile_department_data in profile_departments_data.items %} + + {# department name #} + + {# relation information #} + + + {% endfor %} +
{{ department.name }} + + {# user managing the department #} + {% if profile_department_data|dict_get:"is_manager" %} + + + + + {% endif %} + {# user managing units #} + {% with managing_units=profile_department_data|dict_get:"managing_units" %} + {% if managing_units|length > 0 %} + + + + + {% endif %} + {% endwith %} + {# user teaching units #} + {% with teaching_units=profile_department_data|dict_get:"teaching_units" %} + {% if teaching_units|length > 0 %} + + + + + {% endif %} + {% endwith %} + {# user studying groups #} + {% with student_groups=profile_department_data|dict_get:"student_groups" %} + {% if student_groups|length > 0 %} + + + + + {% endif %} + {% endwith %} +
Responsable de Département/
Responsable d'UE + {% for managing_unit in managing_units %} + {{ managing_unit.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
Enseignant + {% for teaching_unit in teaching_units %} + {{ teaching_unit.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
Groupe Étudiant + {% for student_group in student_groups %} + {{ student_group.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
+
{% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session.html b/Palto/Palto/templates/Palto/teaching_session.html index 5a6c99c..c71e13d 100644 --- a/Palto/Palto/templates/Palto/teaching_session.html +++ b/Palto/Palto/templates/Palto/teaching_session.html @@ -1,27 +1,66 @@ {% extends "Palto/base.html" %} +{% load dict_tags %} {% block body %} - {# table of all the sessions #} + {# session's information #} + + + + + + + + + + + + + + + + + + + + + + + + + +
Identifiant{{ session.id }}
Début{{ session.start }}
Durée{{ session.duration }}
Unité d'Enseignement{{ session.unit }}
Enseignant{{ session.teacher }}
Groupe{{ session.group }}
+ + {# session's students information #} - - - - - - + + + - - - - - - - - + {% for student, session_student_data in session_students_data.items %} + + + + + + {% endfor %}
IdentifiantDébutDuréeUnité d'EnseignementEnseignantGroupeElèvePrésenceAbsence
{{ session.id }}{{ session.start }}{{ session.duration }}{{ session.unit }}{{ session.teacher }}{{ session.group }}
{{ student }} + {% with attendance=session_student_data|dict_get:"attendance" %} + {% if attendance != None %} + {{ attendance.date }} + {% endif %} + {% endwith %} + + {% with absence=session_student_data|dict_get:"attendance" %} + {% if absence != None %} + ... + {% endif %} + {% endwith %} +
+ + {# TODO(Raphaël): export boutton #} {% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session_list.html b/Palto/Palto/templates/Palto/teaching_session_list.html index f9ecc3e..6cbf08c 100644 --- a/Palto/Palto/templates/Palto/teaching_session_list.html +++ b/Palto/Palto/templates/Palto/teaching_session_list.html @@ -6,16 +6,21 @@ Identifiant - Début - Durée + UE + Horaire + Enseignant + Effectif + {# show the information for every session #} {% for session in sessions %} - {{ session.id }} - {{ session.start }} - {{ session.duration }} + {{ session.short_id }} + {{ session.unit.name }} + {{ session.start }}
{{ session.end }} + {{ session.teacher }} + {{ session.attendances.all|length }} / {{ session.group.students.all|length }} {% endfor %} diff --git a/Palto/Palto/templatetags/__init__.py b/Palto/Palto/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Palto/Palto/templatetags/dict_tags.py b/Palto/Palto/templatetags/dict_tags.py new file mode 100644 index 0000000..887312f --- /dev/null +++ b/Palto/Palto/templatetags/dict_tags.py @@ -0,0 +1,6 @@ +from django.template.defaulttags import register + + +@register.filter +def dict_get(d: dict, key: str): + return d.get(key) diff --git a/Palto/Palto/utils.py b/Palto/Palto/utils.py new file mode 100644 index 0000000..06a968a --- /dev/null +++ b/Palto/Palto/utils.py @@ -0,0 +1,15 @@ +from typing import Optional + +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Model, Manager + + +def get_object_or_none(manager: Manager, *args, **kwargs) -> Optional[Model]: + """ + Similar to the Manager.get method, but return None instead of raising an error. + """ + + try: + return manager.get(*args, **kwargs) + except ObjectDoesNotExist: + return None diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index b9ac81f..ead12c6 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -13,7 +13,7 @@ from django.shortcuts import render, get_object_or_404, redirect from Palto.Palto import models from Palto.Palto.forms import LoginForm - +from Palto.Palto.utils import get_object_or_none ELEMENT_PER_PAGE: int = 30 @@ -71,12 +71,25 @@ def profile_view(request: WSGIRequest, profile_id: uuid.UUID = None): # get the corresponding user from its id. profile = get_object_or_404(models.User, id=profile_id) + # prepare the data and the "complex" query for the template + profile_departments_data = { + department: { + "is_manager": profile in department.managers.all(), + "managing_units": models.TeachingUnit.objects.filter(department=department, managers=profile).all(), + "teaching_units": models.TeachingUnit.objects.filter(department=department, teachers=profile).all(), + "student_groups": models.StudentGroup.objects.filter(department=department, students=profile).all(), + } + + for department in profile.related_departments + } + # render the page return render( request, "Palto/profile.html", context=dict( - profile=profile + profile=profile, + profile_departments_data=profile_departments_data, ) ) @@ -110,11 +123,30 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): # TODO: syntaxic sugar session.visible_by_user(request.user) return HttpResponseForbidden() + # prepare the data and the "complex" query for the template + session_students_data = { + student: { + "attendance": get_object_or_none( + models.Attendance.objects, + session=session, + student=student + ), + "absence": get_object_or_none( + models.Absence.objects, + student=student, + start__gte=session.start, end__lte=session.end + ), + } + + for student in session.group.students.all() + } + # render the page return render( request, "Palto/teaching_session.html", context=dict( - session=session + session=session, + session_students_data=session_students_data, ) - ) \ No newline at end of file + ) From 16264a86a5a9e0bcdadfda040ee296467f881091 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 14 Dec 2023 20:56:48 +0100 Subject: [PATCH 5/8] implemented teaching unit and absence pages --- Palto/Palto/migrations/0001_initial.py | 166 ------------------ Palto/Palto/templates/Palto/absence.html | 35 ++++ Palto/Palto/templates/Palto/profile.html | 136 +++++++------- .../templates/Palto/teaching_session.html | 6 +- .../Palto/teaching_session_list.html | 2 +- .../Palto/templates/Palto/teaching_unit.html | 60 +++++++ Palto/Palto/urls.py | 9 + Palto/Palto/views.py | 47 ++++- 8 files changed, 222 insertions(+), 239 deletions(-) delete mode 100644 Palto/Palto/migrations/0001_initial.py create mode 100644 Palto/Palto/templates/Palto/absence.html create mode 100644 Palto/Palto/templates/Palto/teaching_unit.html diff --git a/Palto/Palto/migrations/0001_initial.py b/Palto/Palto/migrations/0001_initial.py deleted file mode 100644 index 56a14d7..0000000 --- a/Palto/Palto/migrations/0001_initial.py +++ /dev/null @@ -1,166 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-10 11:31 - -import Palto.Palto.models -from django.conf import settings -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import uuid - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='Absence', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('message', models.TextField()), - ('start', models.DateTimeField()), - ('end', models.DateTimeField()), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.CreateModel( - name='Department', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=64, unique=True)), - ('email', models.EmailField(max_length=254)), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.CreateModel( - name='StudentGroup', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128)), - ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_groups', to='Palto.department')), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.CreateModel( - name='User', - fields=[ - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - migrations.CreateModel( - name='TeachingUnit', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=64)), - ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_units', to='Palto.department')), - ('managers', models.ManyToManyField(blank=True, related_name='managing_units', to=settings.AUTH_USER_MODEL)), - ('student_groups', models.ManyToManyField(blank=True, related_name='studying_units', to='Palto.studentgroup')), - ('teachers', models.ManyToManyField(blank=True, related_name='teaching_units', to=settings.AUTH_USER_MODEL)), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.CreateModel( - name='TeachingSession', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('start', models.DateTimeField()), - ('duration', models.DurationField()), - ('note', models.TextField(blank=True)), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_sessions', to='Palto.studentgroup')), - ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_sessions', to=settings.AUTH_USER_MODEL)), - ('unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to='Palto.teachingunit')), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.AddField( - model_name='studentgroup', - name='owner', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owning_groups', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='studentgroup', - name='students', - field=models.ManyToManyField(blank=True, related_name='student_groups', to=settings.AUTH_USER_MODEL), - ), - migrations.CreateModel( - name='StudentCard', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('uid', models.BinaryField(max_length=7)), - ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_cards', to='Palto.department')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_cards', to=settings.AUTH_USER_MODEL)), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.AddField( - model_name='department', - name='managers', - field=models.ManyToManyField(blank=True, related_name='managing_departments', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='department', - name='students', - field=models.ManyToManyField(blank=True, related_name='studying_departments', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='department', - name='teachers', - field=models.ManyToManyField(blank=True, related_name='teaching_departments', to=settings.AUTH_USER_MODEL), - ), - migrations.CreateModel( - name='Attendance', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('date', models.DateTimeField()), - ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='Palto.teachingsession')), - ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attended_sessions', to=settings.AUTH_USER_MODEL)), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.CreateModel( - name='AbsenceAttachment', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('content', models.FileField(upload_to='absence/attachment/')), - ('absence', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='Palto.absence')), - ], - bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), - ), - migrations.AddField( - model_name='absence', - name='department', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='Palto.department'), - ), - migrations.AddField( - model_name='absence', - name='student', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/Palto/Palto/templates/Palto/absence.html b/Palto/Palto/templates/Palto/absence.html new file mode 100644 index 0000000..0ade496 --- /dev/null +++ b/Palto/Palto/templates/Palto/absence.html @@ -0,0 +1,35 @@ +{% extends "Palto/base.html" %} + +{% block body %} + {# absence's information #} + + + + + + + + + + + + + + + + + +
Identifiant{{ absence.id }}
Département{{ absence.department }}
Etudiant{{ absence.student }}
Période{{ absence.start }}
{{ absence.end }}
+ + {# absence's message #} +

+ {{ absence.message }} +

+ + {# absence's attachments #} +
+ {% for attachment in absence.attachments.all %} + {{ attachment.content.name }} + {% endfor %} +
+{% endblock %} diff --git a/Palto/Palto/templates/Palto/profile.html b/Palto/Palto/templates/Palto/profile.html index 6ed7d5f..aaf8ad5 100644 --- a/Palto/Palto/templates/Palto/profile.html +++ b/Palto/Palto/templates/Palto/profile.html @@ -2,73 +2,75 @@ {% load dict_tags %} {% block body %} -
- {{ profile.username }} - {{ profile.email }} - {% if profile.is_superuser %}Administrator{% endif %} + {{ profile.username }} + {{ profile.email }} + {% if profile.is_superuser %}Administrator{% endif %} - {# user related departments table #} - - {% for department, profile_department_data in profile_departments_data.items %} - - {# department name #} - - {# relation information #} - + + + + {% endif %} + {% endwith %} + {# user studying groups #} + {% with student_groups=profile_department_data|dict_get:"student_groups" %} + {% if student_groups|length > 0 %} + + + + + {% endif %} + {% endwith %} +
{{ department.name }} - - {# user managing the department #} - {% if profile_department_data|dict_get:"is_manager" %} - - - - + {# user related departments table #} +
Responsable de Département/
+ {% for department, profile_department_data in profile_departments_data.items %} + + {# department name #} + + {# relation information #} + - - {% endfor %} -
{{ department.name }} + + {# user managing the department #} + {% if profile_department_data|dict_get:"is_manager" %} + + + + + {% endif %} + {# user managing units #} + {% with managing_units=profile_department_data|dict_get:"managing_units" %} + {% if managing_units|length > 0 %} + + + + {% endif %} - {# user managing units #} - {% with managing_units=profile_department_data|dict_get:"managing_units" %} - {% if managing_units|length > 0 %} - - - - - {% endif %} - {% endwith %} - {# user teaching units #} - {% with teaching_units=profile_department_data|dict_get:"teaching_units" %} - {% if teaching_units|length > 0 %} - - - - - {% endif %} - {% endwith %} - {# user studying groups #} - {% with student_groups=profile_department_data|dict_get:"student_groups" %} - {% if student_groups|length > 0 %} - - - - - {% endif %} - {% endwith %} -
Responsable de Département/
Responsable d'UE + {% for managing_unit in managing_units %} + + {{ managing_unit.name }} + + {% if not forloop.last %}
{% endif %} + {% endfor %} +
Responsable d'UE - {% for managing_unit in managing_units %} - {{ managing_unit.name }} - {% if not forloop.last %}
{% endif %} - {% endfor %} -
Enseignant - {% for teaching_unit in teaching_units %} - {{ teaching_unit.name }} - {% if not forloop.last %}
{% endif %} - {% endfor %} -
Groupe Étudiant - {% for student_group in student_groups %} - {{ student_group.name }} - {% if not forloop.last %}
{% endif %} - {% endfor %} -
-
- + {% endwith %} + {# user teaching units #} + {% with teaching_units=profile_department_data|dict_get:"teaching_units" %} + {% if teaching_units|length > 0 %} +
Enseignant + {% for teaching_unit in teaching_units %} + + {{ teaching_unit.name }} + + {% if not forloop.last %}
{% endif %} + {% endfor %} +
Groupe Étudiant + {% for student_group in student_groups %} + {{ student_group.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
+ + + {% endfor %} + {% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session.html b/Palto/Palto/templates/Palto/teaching_session.html index c71e13d..e61df75 100644 --- a/Palto/Palto/templates/Palto/teaching_session.html +++ b/Palto/Palto/templates/Palto/teaching_session.html @@ -18,7 +18,7 @@ Unité d'Enseignement - {{ session.unit }} + {{ session.unit }} Enseignant @@ -51,9 +51,9 @@ {% endwith %} - {% with absence=session_student_data|dict_get:"attendance" %} + {% with absence=session_student_data|dict_get:"absence" %} {% if absence != None %} - ... + Détails {% endif %} {% endwith %} diff --git a/Palto/Palto/templates/Palto/teaching_session_list.html b/Palto/Palto/templates/Palto/teaching_session_list.html index 6cbf08c..538d830 100644 --- a/Palto/Palto/templates/Palto/teaching_session_list.html +++ b/Palto/Palto/templates/Palto/teaching_session_list.html @@ -17,7 +17,7 @@ {% for session in sessions %} {{ session.short_id }} - {{ session.unit.name }} + {{ session.unit.name }} {{ session.start }}
{{ session.end }} {{ session.teacher }} {{ session.attendances.all|length }} / {{ session.group.students.all|length }} diff --git a/Palto/Palto/templates/Palto/teaching_unit.html b/Palto/Palto/templates/Palto/teaching_unit.html new file mode 100644 index 0000000..bb8f173 --- /dev/null +++ b/Palto/Palto/templates/Palto/teaching_unit.html @@ -0,0 +1,60 @@ +{% extends "Palto/base.html" %} +{% load dict_tags %} + +{% block body %} + {# unit's information #} + + + + + + + + + + + + + + + + + + + + + +
Identifiant{{ unit.id }}
Nom{{ unit.name }}
Département{{ unit.department.name }}
Mail{% if unit.email != None %}{{ unit.email }}{% else %} / {% endif %}
Sessions{{ unit.sessions.all|length }}
+ + {# unit's managers #} + + + + + + + + {% for manager in unit.managers.all %} + + + + {% endfor %} + +
Responsables
{{ manager }}
+ + {# unit's teachers #} + + + + + + + + {% for teacher in unit.teachers.all %} + + + + {% endfor %} + +
Enseignants
{{ teacher }}
+{% endblock %} diff --git a/Palto/Palto/urls.py b/Palto/Palto/urls.py index 357120f..e848bbf 100644 --- a/Palto/Palto/urls.py +++ b/Palto/Palto/urls.py @@ -18,6 +18,15 @@ urlpatterns = [ path("logout/", views.logout_view, name="logout"), path("profile/", views.profile_view, name="my_profile"), path("profile//", views.profile_view, name="profile"), + + # Units + path("teaching_units//", views.teaching_unit_view, name="teaching_unit"), + + # Sessions path("teaching_sessions/", views.teaching_session_list_view, name="teaching_session_list"), path("teaching_sessions//", views.teaching_session_view, name="teaching_session"), + + # Absences + path("absences//", views.absence_view, name="absence"), + # TODO: new absence ] diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index ead12c6..f7886f5 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -71,6 +71,10 @@ def profile_view(request: WSGIRequest, profile_id: uuid.UUID = None): # get the corresponding user from its id. profile = get_object_or_404(models.User, id=profile_id) + # check if the user is allowed to see this specific object + if profile not in models.User.all_visible_by_user(request.user): + return HttpResponseForbidden() + # prepare the data and the "complex" query for the template profile_departments_data = { department: { @@ -115,10 +119,30 @@ def teaching_session_list_view(request: WSGIRequest): ) +@login_required +def teaching_unit_view(request: WSGIRequest, unit_id: uuid.UUID): + unit = get_object_or_404(models.TeachingUnit, id=unit_id) + + # check if the user is allowed to see this specific object + if unit not in models.TeachingUnit.all_visible_by_user(request.user): + # TODO: syntaxic sugar session.visible_by_user(request.user) + return HttpResponseForbidden() + + # render the page + return render( + request, + "Palto/teaching_unit.html", + context=dict( + unit=unit, + ) + ) + + @login_required def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): session = get_object_or_404(models.TeachingSession, id=session_id) + # check if the user is allowed to see this specific object if session not in models.TeachingSession.all_visible_by_user(request.user): # TODO: syntaxic sugar session.visible_by_user(request.user) return HttpResponseForbidden() @@ -134,8 +158,8 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): "absence": get_object_or_none( models.Absence.objects, student=student, - start__gte=session.start, end__lte=session.end - ), + start__lte=session.start, end__gte=session.end + ), # TODO: property ? } for student in session.group.students.all() @@ -150,3 +174,22 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): session_students_data=session_students_data, ) ) + + +@login_required +def absence_view(request: WSGIRequest, absence_id: uuid.UUID): + absence = get_object_or_404(models.Absence, id=absence_id) + + # check if the user is allowed to see this specific object + if absence not in models.Absence.all_visible_by_user(request.user): + # TODO: syntaxic sugar session.visible_by_user(request.user) + return HttpResponseForbidden() + + # render the page + return render( + request, + "Palto/absence.html", + context=dict( + absence=absence, + ) + ) From acc9ee66e79e677472bf8ced5812da64a3409b0b Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 14 Dec 2023 20:57:08 +0100 Subject: [PATCH 6/8] added email to teaching unit --- Palto/Palto/admin.py | 4 ++-- Palto/Palto/factories.py | 6 ++---- Palto/Palto/models.py | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Palto/Palto/admin.py b/Palto/Palto/admin.py index 86277a2..10d3d9f 100644 --- a/Palto/Palto/admin.py +++ b/Palto/Palto/admin.py @@ -34,8 +34,8 @@ class AdminStudentGroup(admin.ModelAdmin): @admin.register(models.TeachingUnit) class AdminTeachingUnit(admin.ModelAdmin): - list_display = ("id", "name") - search_fields = ("id", "name") + list_display = ("id", "name", "email") + search_fields = ("id", "name", "email") @admin.register(models.StudentCard) diff --git a/Palto/Palto/factories.py b/Palto/Palto/factories.py index 21a7baa..15e1079 100644 --- a/Palto/Palto/factories.py +++ b/Palto/Palto/factories.py @@ -90,10 +90,8 @@ class FakeTeachingUnitFactory(factory.django.DjangoModelFactory): model = models.TeachingUnit name: str = factory.Faker("administrative_unit") - - department: models.Department = factory.SubFactory( - FakeDepartmentFactory - ) + email: str = factory.Faker("company_email") + department: models.Department = factory.SubFactory(FakeDepartmentFactory) @factory.post_generation def managers(self, create, extracted, **kwargs): diff --git a/Palto/Palto/models.py b/Palto/Palto/models.py index 761ef10..ba54285 100644 --- a/Palto/Palto/models.py +++ b/Palto/Palto/models.py @@ -318,6 +318,7 @@ class TeachingUnit(models.Model, ModelPermissionHelper): id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36) name: str = models.CharField(max_length=64) + email: str = models.EmailField(null=True, blank=True) department = models.ForeignKey(to=Department, on_delete=models.CASCADE, related_name="teaching_units") From 8db28b983864e9f3553788b885e69b476424aecb Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 14 Dec 2023 22:56:22 +0100 Subject: [PATCH 7/8] added create absence page (missing attachments) --- Palto/Palto/admin.py | 2 +- Palto/Palto/api/v1/serializers.py | 2 +- Palto/Palto/forms.py | 20 +++++++ Palto/Palto/models.py | 2 +- Palto/Palto/templates/Palto/absence_new.html | 11 ++++ .../Palto/{absence.html => absence_view.html} | 6 +- Palto/Palto/templates/Palto/login.html | 6 +- Palto/Palto/templates/Palto/profile.html | 4 +- .../Palto/teaching_session_list.html | 4 +- ...ession.html => teaching_session_view.html} | 4 +- ...hing_unit.html => teaching_unit_view.html} | 2 +- Palto/Palto/urls.py | 8 +-- Palto/Palto/views.py | 56 +++++++++++++++---- Palto/settings.py | 2 +- 14 files changed, 99 insertions(+), 30 deletions(-) create mode 100644 Palto/Palto/templates/Palto/absence_new.html rename Palto/Palto/templates/Palto/{absence.html => absence_view.html} (76%) rename Palto/Palto/templates/Palto/{teaching_session.html => teaching_session_view.html} (90%) rename Palto/Palto/templates/Palto/{teaching_unit.html => teaching_unit_view.html} (94%) diff --git a/Palto/Palto/admin.py b/Palto/Palto/admin.py index 10d3d9f..67d2e10 100644 --- a/Palto/Palto/admin.py +++ b/Palto/Palto/admin.py @@ -9,7 +9,7 @@ from django.contrib import admin from . import models -# TODO: plus de list_filter sur "department" ? +# TODO(Faraphel): plus de list_filter sur "department" ? # Register your models here. diff --git a/Palto/Palto/api/v1/serializers.py b/Palto/Palto/api/v1/serializers.py index f0aadef..fb9ef23 100644 --- a/Palto/Palto/api/v1/serializers.py +++ b/Palto/Palto/api/v1/serializers.py @@ -12,7 +12,7 @@ from rest_framework.exceptions import PermissionDenied from Palto.Palto import models -# TODO: voir les relations inversées ? +# TODO(Faraphel): voir les relations inversées ? class ModelSerializerContrains(serializers.ModelSerializer): diff --git a/Palto/Palto/forms.py b/Palto/Palto/forms.py index 98c65bc..eb9aebd 100644 --- a/Palto/Palto/forms.py +++ b/Palto/Palto/forms.py @@ -1,6 +1,26 @@ from django import forms +from Palto.Palto import models + + +# Users + class LoginForm(forms.Form): username = forms.CharField() password = forms.CharField(widget=forms.PasswordInput) + + +# Objects + + +class NewAbsenceForm(forms.Form): + department = forms.ModelChoiceField(queryset=None) + start = forms.DateTimeField(widget=forms.TextInput(attrs=dict(type='datetime-local'))) + end = forms.DateTimeField(widget=forms.TextInput(attrs=dict(type='datetime-local'))) + message = forms.CharField(widget=forms.Textarea) + + def __init__(self, student: models.User, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields["department"].queryset = student.studying_departments.all() diff --git a/Palto/Palto/models.py b/Palto/Palto/models.py index ba54285..28ad2f6 100644 --- a/Palto/Palto/models.py +++ b/Palto/Palto/models.py @@ -15,7 +15,7 @@ from django.db import models from django.db.models import QuerySet, Q, F -# TODO(Raphaël): split permissions from models for readability +# TODO(Faraphel): split permissions from models for readability class ModelPermissionHelper: diff --git a/Palto/Palto/templates/Palto/absence_new.html b/Palto/Palto/templates/Palto/absence_new.html new file mode 100644 index 0000000..f9dcb66 --- /dev/null +++ b/Palto/Palto/templates/Palto/absence_new.html @@ -0,0 +1,11 @@ +{% extends "Palto/base.html" %} + +{% block body %} +
+ {% csrf_token %} + + {{ form_new_absence.as_table }} +
+ +
+{% endblock %} diff --git a/Palto/Palto/templates/Palto/absence.html b/Palto/Palto/templates/Palto/absence_view.html similarity index 76% rename from Palto/Palto/templates/Palto/absence.html rename to Palto/Palto/templates/Palto/absence_view.html index 0ade496..6f93217 100644 --- a/Palto/Palto/templates/Palto/absence.html +++ b/Palto/Palto/templates/Palto/absence_view.html @@ -9,11 +9,11 @@ Département - {{ absence.department }} + {{ absence.department }} - Etudiant - {{ absence.student }} + Étudiant + {{ absence.student }} Période diff --git a/Palto/Palto/templates/Palto/login.html b/Palto/Palto/templates/Palto/login.html index 8672a95..d14cca1 100644 --- a/Palto/Palto/templates/Palto/login.html +++ b/Palto/Palto/templates/Palto/login.html @@ -3,7 +3,9 @@ {% block body %}
{% csrf_token %} - {{ form_login }} - + + {{ form_login.as_table }} +
+
{% endblock %} \ No newline at end of file diff --git a/Palto/Palto/templates/Palto/profile.html b/Palto/Palto/templates/Palto/profile.html index aaf8ad5..d2b55a3 100644 --- a/Palto/Palto/templates/Palto/profile.html +++ b/Palto/Palto/templates/Palto/profile.html @@ -29,7 +29,7 @@ Responsable d'UE {% for managing_unit in managing_units %} - + {{ managing_unit.name }} {% if not forloop.last %}
{% endif %} @@ -45,7 +45,7 @@ Enseignant {% for teaching_unit in teaching_units %} - + {{ teaching_unit.name }} {% if not forloop.last %}
{% endif %} diff --git a/Palto/Palto/templates/Palto/teaching_session_list.html b/Palto/Palto/templates/Palto/teaching_session_list.html index 538d830..715d933 100644 --- a/Palto/Palto/templates/Palto/teaching_session_list.html +++ b/Palto/Palto/templates/Palto/teaching_session_list.html @@ -16,8 +16,8 @@ {# show the information for every session #} {% for session in sessions %} - {{ session.short_id }} - {{ session.unit.name }} + {{ session.short_id }} + {{ session.unit.name }} {{ session.start }}
{{ session.end }} {{ session.teacher }} {{ session.attendances.all|length }} / {{ session.group.students.all|length }} diff --git a/Palto/Palto/templates/Palto/teaching_session.html b/Palto/Palto/templates/Palto/teaching_session_view.html similarity index 90% rename from Palto/Palto/templates/Palto/teaching_session.html rename to Palto/Palto/templates/Palto/teaching_session_view.html index e61df75..3a26783 100644 --- a/Palto/Palto/templates/Palto/teaching_session.html +++ b/Palto/Palto/templates/Palto/teaching_session_view.html @@ -18,7 +18,7 @@ Unité d'Enseignement - {{ session.unit }} + {{ session.unit }} Enseignant @@ -53,7 +53,7 @@ {% with absence=session_student_data|dict_get:"absence" %} {% if absence != None %} - Détails + Détails {% endif %} {% endwith %} diff --git a/Palto/Palto/templates/Palto/teaching_unit.html b/Palto/Palto/templates/Palto/teaching_unit_view.html similarity index 94% rename from Palto/Palto/templates/Palto/teaching_unit.html rename to Palto/Palto/templates/Palto/teaching_unit_view.html index bb8f173..084b84e 100644 --- a/Palto/Palto/templates/Palto/teaching_unit.html +++ b/Palto/Palto/templates/Palto/teaching_unit_view.html @@ -14,7 +14,7 @@ Département - {{ unit.department.name }} + {{ unit.department.name }} Mail diff --git a/Palto/Palto/urls.py b/Palto/Palto/urls.py index e848bbf..513d372 100644 --- a/Palto/Palto/urls.py +++ b/Palto/Palto/urls.py @@ -20,13 +20,13 @@ urlpatterns = [ path("profile//", views.profile_view, name="profile"), # Units - path("teaching_units//", views.teaching_unit_view, name="teaching_unit"), + path("teaching_units/view//", views.teaching_unit_view, name="teaching_unit_view"), # Sessions path("teaching_sessions/", views.teaching_session_list_view, name="teaching_session_list"), - path("teaching_sessions//", views.teaching_session_view, name="teaching_session"), + path("teaching_sessions/view//", views.teaching_session_view, name="teaching_session_view"), # Absences - path("absences//", views.absence_view, name="absence"), - # TODO: new absence + path("absences/view//", views.absence_view, name="absence_view"), + path("absences/new/", views.new_absence_view, name="absence_new"), ] diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index f7886f5..7e0bbb7 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -8,11 +8,11 @@ from django.contrib.auth import login, authenticate, logout from django.contrib.auth.decorators import login_required from django.core.handlers.wsgi import WSGIRequest from django.core.paginator import Paginator +from django.db import IntegrityError from django.http import HttpResponseForbidden from django.shortcuts import render, get_object_or_404, redirect -from Palto.Palto import models -from Palto.Palto.forms import LoginForm +from Palto.Palto import models, forms from Palto.Palto.utils import get_object_or_none ELEMENT_PER_PAGE: int = 30 @@ -25,7 +25,7 @@ def homepage_view(request: WSGIRequest): def login_view(request: WSGIRequest): # create a login form - form_login = LoginForm(request.POST) + form_login = forms.LoginForm(request.POST) if form_login.is_valid(): # try to authenticate this user with the credentials @@ -125,13 +125,13 @@ def teaching_unit_view(request: WSGIRequest, unit_id: uuid.UUID): # check if the user is allowed to see this specific object if unit not in models.TeachingUnit.all_visible_by_user(request.user): - # TODO: syntaxic sugar session.visible_by_user(request.user) + # TODO(Faraphel): syntaxic sugar session.visible_by_user(request.user) return HttpResponseForbidden() # render the page return render( request, - "Palto/teaching_unit.html", + "Palto/teaching_unit_view.html", context=dict( unit=unit, ) @@ -144,7 +144,7 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): # check if the user is allowed to see this specific object if session not in models.TeachingSession.all_visible_by_user(request.user): - # TODO: syntaxic sugar session.visible_by_user(request.user) + # TODO(Faraphel): syntaxic sugar session.visible_by_user(request.user) return HttpResponseForbidden() # prepare the data and the "complex" query for the template @@ -159,7 +159,7 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): models.Absence.objects, student=student, start__lte=session.start, end__gte=session.end - ), # TODO: property ? + ), # TODO(Faraphel): property ? } for student in session.group.students.all() @@ -168,7 +168,7 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): # render the page return render( request, - "Palto/teaching_session.html", + "Palto/teaching_session_view.html", context=dict( session=session, session_students_data=session_students_data, @@ -182,14 +182,50 @@ def absence_view(request: WSGIRequest, absence_id: uuid.UUID): # check if the user is allowed to see this specific object if absence not in models.Absence.all_visible_by_user(request.user): - # TODO: syntaxic sugar session.visible_by_user(request.user) + # TODO(Faraphel): syntaxic sugar session.visible_by_user(request.user) return HttpResponseForbidden() # render the page return render( request, - "Palto/absence.html", + "Palto/absence_view.html", context=dict( absence=absence, ) ) + + +@login_required +def new_absence_view(request: WSGIRequest): + # check if the user can create an absence + if not models.Absence.can_user_create(request.user): + return HttpResponseForbidden() + + # create a form for the new absence + form_new_absence = forms.NewAbsenceForm(request.user, request.POST) + + if form_new_absence.is_valid(): + try: + models.Absence.objects.create( + student=request.user, + start=form_new_absence.cleaned_data["start"], + end=form_new_absence.cleaned_data["end"], + department=form_new_absence.cleaned_data["department"], + message=form_new_absence.cleaned_data["message"], + ) + except IntegrityError: + form_new_absence.add_error(None, "This absence already exists.") + + else: + return redirect("Palto:homepage") # TODO(Faraphel): redirect to absence list + + # TODO(Faraphel): add attachments to the forms + + # render the page + return render( + request, + "Palto/absence_new.html", + context=dict( + form_new_absence=form_new_absence, + ) + ) diff --git a/Palto/settings.py b/Palto/settings.py index 32f44ff..efe4b7a 100644 --- a/Palto/settings.py +++ b/Palto/settings.py @@ -191,7 +191,7 @@ AUTH_USER_MODEL = "Palto.User" # CORS settings -# TODO(Raphaël): Only in debug ! +# TODO(Faraphel): Only in debug ! CORS_ORIGIN_ALLOW_ALL = True From c3c462393177920f7842af4cf6da2232904bcf90 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 15 Dec 2023 21:15:02 +0100 Subject: [PATCH 8/8] added uploading files to absences --- Palto/Palto/forms.py | 19 ++++++++++++ Palto/Palto/templates/Palto/absence_new.html | 2 +- Palto/Palto/templates/Palto/absence_view.html | 2 +- Palto/Palto/views.py | 31 ++++++++++++------- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Palto/Palto/forms.py b/Palto/Palto/forms.py index eb9aebd..f1116ae 100644 --- a/Palto/Palto/forms.py +++ b/Palto/Palto/forms.py @@ -3,6 +3,24 @@ from django import forms from Palto.Palto import models +# Common +class MultipleFileInput(forms.ClearableFileInput): + allow_multiple_selected = True + + +class MultipleFileField(forms.FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + file_cleaner = super().clean + if isinstance(data, (list, tuple)): + return [file_cleaner(d, initial) for d in data] + else: + return file_cleaner(data, initial) + + # Users @@ -19,6 +37,7 @@ class NewAbsenceForm(forms.Form): start = forms.DateTimeField(widget=forms.TextInput(attrs=dict(type='datetime-local'))) end = forms.DateTimeField(widget=forms.TextInput(attrs=dict(type='datetime-local'))) message = forms.CharField(widget=forms.Textarea) + attachments = MultipleFileField(required=False) def __init__(self, student: models.User, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/Palto/Palto/templates/Palto/absence_new.html b/Palto/Palto/templates/Palto/absence_new.html index f9dcb66..e73e87c 100644 --- a/Palto/Palto/templates/Palto/absence_new.html +++ b/Palto/Palto/templates/Palto/absence_new.html @@ -1,7 +1,7 @@ {% extends "Palto/base.html" %} {% block body %} -
+ {% csrf_token %} {{ form_new_absence.as_table }} diff --git a/Palto/Palto/templates/Palto/absence_view.html b/Palto/Palto/templates/Palto/absence_view.html index 6f93217..91f8e29 100644 --- a/Palto/Palto/templates/Palto/absence_view.html +++ b/Palto/Palto/templates/Palto/absence_view.html @@ -29,7 +29,7 @@ {# absence's attachments #}
{% for attachment in absence.attachments.all %} - {{ attachment.content.name }} + {{ attachment.content.name }} {% endfor %}
{% endblock %} diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index 7e0bbb7..ca4eabe 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -202,24 +202,31 @@ def new_absence_view(request: WSGIRequest): return HttpResponseForbidden() # create a form for the new absence - form_new_absence = forms.NewAbsenceForm(request.user, request.POST) + form_new_absence = forms.NewAbsenceForm(request.user, request.POST, request.FILES) if form_new_absence.is_valid(): - try: - models.Absence.objects.create( - student=request.user, - start=form_new_absence.cleaned_data["start"], - end=form_new_absence.cleaned_data["end"], - department=form_new_absence.cleaned_data["department"], - message=form_new_absence.cleaned_data["message"], - ) - except IntegrityError: + print(form_new_absence.files, form_new_absence.cleaned_data) + + absence, is_created = models.Absence.objects.get_or_create( + student=request.user, + start=form_new_absence.cleaned_data["start"], + end=form_new_absence.cleaned_data["end"], + department=form_new_absence.cleaned_data["department"], + message=form_new_absence.cleaned_data["message"], + ) + + if not is_created: + # if the absence already existed, show an error form_new_absence.add_error(None, "This absence already exists.") else: - return redirect("Palto:homepage") # TODO(Faraphel): redirect to absence list + # add the attachments files to the absence + for file in form_new_absence.cleaned_data["attachments"]: + absence.attachments.create( + content=file + ) - # TODO(Faraphel): add attachments to the forms + return redirect("Palto:homepage") # TODO(Faraphel): redirect to absence list # render the page return render(