Marketplace, CSS, iCal and a lot of things... #4

Closed
valentin wants to merge 59 commits from dev into main
14 changed files with 401 additions and 21 deletions
Showing only changes of commit db6cf3390f - Show all commits

View File

@ -36,7 +36,7 @@ def to_calendar(student, term, include_EDT: bool = True):
summary = f"Colle {colle.slot.subject} ({colle.slot.colleur})" summary = f"Colle {colle.slot.subject} ({colle.slot.colleur})"
event.add("summary", summary) event.add("summary", summary)
start = colle.datetime() start = colle.datetime
fin = start + colle.slot.duration fin = start + colle.slot.duration
event.add("dtstart", start, parameters={"tzid": LOCAL_TZ}) event.add("dtstart", start, parameters={"tzid": LOCAL_TZ})
@ -56,7 +56,7 @@ def to_calendar(student, term, include_EDT: bool = True):
event.add("organizer", organizer) event.add("organizer", organizer)
for e in colle.final_group(): for e in colle.final_group():
attendee = vCalAddress("mailto:{emailize(e.name, first_name=e.first_name)}") attendee = vCalAddress(f"mailto:{emailize(e.last_name, first_name=e.first_name)}")
attendee.params["role"] = vText("Etudiant") attendee.params["role"] = vText("Etudiant")
attendee.params["cn"] = vText(str(e)) attendee.params["cn"] = vText(str(e))

View File

@ -0,0 +1,255 @@
# Generated by Django 5.0.4 on 2024-05-02 20:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('colloscope', '0012_rename_lycee_school_rename_creneau_slot_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='colle',
options={'ordering': ['slot__term__cls', 'slot__subject__description', 'slot__colleur__name', 'date', 'slot__time']},
),
migrations.AlterModelOptions(
name='slot',
options={'verbose_name_plural': 'slots'},
),
migrations.AlterModelOptions(
name='student',
options={'ordering': ['cls', 'last_name', 'first_name']},
),
migrations.AlterModelOptions(
name='term',
options={'ordering': ['begin']},
),
migrations.RemoveConstraint(
model_name='calendarlink',
name='unique_etudiant_periode_combination',
),
migrations.RenameField(
model_name='calendarlink',
old_name='code',
new_name='key',
),
migrations.RenameField(
model_name='calendarlink',
old_name='etudiant',
new_name='student',
),
migrations.RenameField(
model_name='calendarlink',
old_name='periode',
new_name='term',
),
migrations.RenameField(
model_name='class',
old_name='jour_zero',
new_name='day_zero',
),
migrations.RenameField(
model_name='class',
old_name='libelle',
new_name='description',
),
migrations.RenameField(
model_name='class',
old_name='annee',
new_name='year',
),
migrations.RenameField(
model_name='colle',
old_name='groupes',
new_name='groups',
),
migrations.RenameField(
model_name='colle',
old_name='creneau',
new_name='slot',
),
migrations.RenameField(
model_name='colleur',
old_name='civilite',
new_name='gender',
),
migrations.RenameField(
model_name='colleur',
old_name='nom',
new_name='name',
),
migrations.RenameField(
model_name='group',
old_name='libelle',
new_name='description',
),
migrations.RenameField(
model_name='group',
old_name='membres',
new_name='members',
),
migrations.RenameField(
model_name='group',
old_name='periode',
new_name='term',
),
migrations.RenameField(
model_name='group',
old_name='critere',
new_name='type',
),
migrations.RenameField(
model_name='grouptype',
old_name='libelle',
new_name='description',
),
migrations.RenameField(
model_name='grouptype',
old_name='periode',
new_name='term',
),
migrations.RenameField(
model_name='member',
old_name='groupe',
new_name='group',
),
migrations.RenameField(
model_name='member',
old_name='etudiant',
new_name='student',
),
migrations.RenameField(
model_name='profile',
old_name='etudiant',
new_name='student',
),
migrations.RenameField(
model_name='profile',
old_name='utilisateur',
new_name='user',
),
migrations.RenameField(
model_name='school',
old_name='libelle',
new_name='description',
),
migrations.RenameField(
model_name='school',
old_name='vacances',
new_name='vacation',
),
migrations.RenameField(
model_name='slot',
old_name='capacite',
new_name='capacity',
),
migrations.RenameField(
model_name='slot',
old_name='jour',
new_name='day',
),
migrations.RenameField(
model_name='slot',
old_name='duree',
new_name='duration',
),
migrations.RenameField(
model_name='slot',
old_name='salle',
new_name='room',
),
migrations.RenameField(
model_name='slot',
old_name='matiere',
new_name='subject',
),
migrations.RenameField(
model_name='slot',
old_name='periode',
new_name='term',
),
migrations.RenameField(
model_name='slot',
old_name='heure',
new_name='time',
),
migrations.RenameField(
model_name='student',
old_name='nom',
new_name='last_name',
),
migrations.RenameField(
model_name='student',
old_name='groupes',
new_name='groups',
),
migrations.RenameField(
model_name='student',
old_name='prenom',
new_name='first_name',
),
migrations.RenameField(
model_name='subject',
old_name='libelle',
new_name='description',
),
migrations.RenameField(
model_name='swap',
old_name='rotation',
new_name='colle',
),
migrations.RenameField(
model_name='swap',
old_name='est_positif',
new_name='enroll',
),
migrations.RenameField(
model_name='swap',
old_name='etudiant',
new_name='student',
),
migrations.RenameField(
model_name='term',
old_name='debut',
new_name='begin',
),
migrations.RenameField(
model_name='term',
old_name='libelle',
new_name='description',
),
migrations.RenameField(
model_name='term',
old_name='fin',
new_name='end',
),
migrations.RemoveField(
model_name='slot',
name='est_colle',
),
migrations.RenameField(
model_name='student',
old_name='classe',
new_name='cls',
),
migrations.RenameField(
model_name='subject',
old_name='classe',
new_name='cls',
),
migrations.RenameField(
model_name='term',
old_name='classe',
new_name='cls',
),
migrations.AddConstraint(
model_name='calendarlink',
constraint=models.UniqueConstraint(fields=('student', 'term'), name='unique_student_term_combination'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 5.0.4 on 2024-05-02 21:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('colloscope', '0013_alter_colle_options_alter_slot_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='group',
options={'ordering': ['term__cls__description', 'term__description', 'description']},
),
migrations.AddField(
model_name='slot',
name='type',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='colloscope.grouptype'),
preserve_default=False,
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-02 21:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('colloscope', '0014_alter_group_options_slot_type'),
]
operations = [
migrations.RenameField(
model_name='class',
old_name='lycee',
new_name='school',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-05 07:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('colloscope', '0015_rename_lycee_class_school'),
]
operations = [
migrations.AlterField(
model_name='colle',
name='groups',
field=models.ManyToManyField(blank=True, to='colloscope.group'),
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 5.0.4 on 2024-05-06 20:22
from datetime import datetime
from pytz import timezone
from django.db import migrations, models
from django.db.migrations import RunPython
def replace_date_with_datetime(apps, schema_editor):
model = apps.get_model('colloscope', 'Colle')
for colle in model.objects.all():
print(colle.slot.time, end="-->")
colle.datetime = datetime.combine(colle.date, colle.slot.time)
colle.datetime = timezone("Europe/Paris").localize(colle.datetime)
print(colle.datetime)
colle.save()
class Migration(migrations.Migration):
dependencies = [
('colloscope', '0016_alter_colle_groups'),
]
operations = [
migrations.AlterModelOptions(
name='slot',
options={'ordering': ['subject', 'colleur', 'day', 'time'], 'verbose_name_plural': 'slots'},
),
migrations.AlterModelOptions(
name='subject',
options={'ordering': ['description']},
),
migrations.AddField(
model_name='colle',
name='datetime',
field=models.DateTimeField(default=datetime(1970, 1, 1, 0, 0, 0)),
),
migrations.RunPython(replace_date_with_datetime),
migrations.RemoveField(
model_name='colle',
name='date',
)
]

View File

@ -149,7 +149,7 @@ class Term(models.Model):
.annotate(swap_plus=Count("swap", filter=Q(swap__enroll=1), distinct=True)) .annotate(swap_plus=Count("swap", filter=Q(swap__enroll=1), distinct=True))
.annotate(swap_minus=Count("swap", filter=Q(swap__enroll=0), distinct=True)) .annotate(swap_minus=Count("swap", filter=Q(swap__enroll=0), distinct=True))
.annotate(volume=F("base_vol") + F("swap_plus") - F("swap_minus")) .annotate(volume=F("base_vol") + F("swap_plus") - F("swap_minus"))
.order_by("date", "slot__time")) .order_by("datetime", "slot__time"))
def query_colles_of_student(self, student) -> QuerySet: def query_colles_of_student(self, student) -> QuerySet:
has_student = ((Q(groups__student=student) has_student = ((Q(groups__student=student)
@ -174,7 +174,7 @@ class Term(models.Model):
| Q(swap__enroll=1, swap__student=student)) | Q(swap__enroll=1, swap__student=student))
return (self.query_colles() return (self.query_colles()
.filter(volume__lt=F("slot__capacity"), date__gte=date.today()) .filter(volume__lt=F("slot__capacity"), datetime__gte=date.today())
.exclude(has_student)) .exclude(has_student))
def __str__(self) -> str: def __str__(self) -> str:
@ -308,12 +308,11 @@ class Slot(models.Model):
class Colle(models.Model): class Colle(models.Model):
class Meta: class Meta:
ordering = ["slot__term__cls", "slot__subject__description", "slot__colleur__name", "date", ordering = ["slot__term__cls", "slot__subject__description", "slot__colleur__name", "datetime"]
"slot__time"]
slot = models.ForeignKey(Slot, on_delete=models.CASCADE) slot = models.ForeignKey(Slot, on_delete=models.CASCADE)
groups = models.ManyToManyField(Group, blank=True) groups = models.ManyToManyField(Group, blank=True)
date = models.DateField() datetime = models.DateTimeField()
def initial_group(self): def initial_group(self):
""" """
@ -385,10 +384,7 @@ class Colle(models.Model):
# func() # func()
def __str__(self): def __str__(self):
return f"Colle {self.slot.subject} ({self.slot.colleur}); {self.date} {self.slot.time} {self.slot.room}. Groupe(s) {{{'; '.join(str(groupe) for groupe in self.groups.all())}}}" return f"Colle {self.slot.subject} ({self.slot.colleur}); {self.datetime} {self.slot.time} {self.slot.room}. Groupe(s) {{{'; '.join(str(groupe) for groupe in self.groups.all())}}}"
def datetime(self):
return datetime.combine(self.date, self.slot.time, tzinfo=timezone("Europe/Paris"))
class Swap(models.Model): class Swap(models.Model):

View File

@ -75,8 +75,8 @@ class PDF(FPDF):
for s in weeks: for s in weeks:
lundi = term.cls.week_beginning_date(s) lundi = term.cls.week_beginning_date(s)
if Colle.objects.filter(slot=c, date__gte=lundi, date__lt=lundi + timedelta(weeks=1)).exists(): if Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1)).exists():
r = Colle.objects.get(slot=c, date__gte=lundi, date__lt=lundi + timedelta(weeks=1)) r = Colle.objects.get(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1))
groups = r.groups groups = r.groups
content = ", ".join(g.description for g in groups.all()) content = ", ".join(g.description for g in groups.all())

View File

@ -1,3 +1,4 @@
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from colloscope.models import * from colloscope.models import *
@ -61,10 +62,21 @@ class SlotSerializer(ModelSerializer):
fields = ["id", "term", "day", "time", "duration", "room", "subject", "colleur", "type", "capacity"] fields = ["id", "term", "day", "time", "duration", "room", "subject", "colleur", "type", "capacity"]
class SwapSerializer(ModelSerializer):
class Meta:
model = Swap
fields = ["enroll", "colle", "student"]
class ColleSerializer(ModelSerializer): class ColleSerializer(ModelSerializer):
base_vol = serializers.IntegerField()
volume = serializers.IntegerField()
slot = SlotSerializer()
swaps = SwapSerializer(source="swap_set", many=True)
class Meta: class Meta:
model = Colle model = Colle
fields = ["id", "slot", "groups", "date"] fields = ["id", "slot", "groups", "datetime", "base_vol", "volume", "swaps"]
class CalendarLinkSerializer(ModelSerializer): class CalendarLinkSerializer(ModelSerializer):

View File

@ -31,7 +31,7 @@ Bienvenue {{ student }}. Votre lycée est {{ term.cls.school.description }}, et
<div class="colle"> <div class="colle">
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span> <span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
<ul> <ul>
<li><i class="fa-solid fa-clock"></i> Le {{ colle.date }} à {{ colle.slot.time }}</li> <li><i class="fa-solid fa-clock"></i> Le {{ colle.datetime|date:"l" }} {{ colle.datetime|date:"DATETIME_FORMAT" }}</li>
<li><i class="fa-solid fa-users"></i> {{ colle.groups.all | print_manager | safe }} ({{ colle.volume }} / {{ colle.slot.capacity }})</li> <li><i class="fa-solid fa-users"></i> {{ colle.groups.all | print_manager | safe }} ({{ colle.volume }} / {{ colle.slot.capacity }})</li>
<li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li> <li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li>
<li><i class="fa-solid fa-circle-exclamation"></i> <li><i class="fa-solid fa-circle-exclamation"></i>

View File

@ -19,7 +19,7 @@
<div class="colle"> <div class="colle">
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span> <span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
<ul> <ul>
<li><i class="fa-solid fa-clock"></i> Le {{ colle.date }} à {{ colle.slot.time }}</li> <li><i class="fa-solid fa-clock"></i> Le {{ colle.datetime|date:"l" }} {{ colle.datetime|date:"DATETIME_FORMAT" }}</li>
<li><i class="fa-solid fa-users"></i> {{ colle.groups.all | print_manager | safe }} ({{ colle.volume }} / {{ colle.slot.capacity }})</li> <li><i class="fa-solid fa-users"></i> {{ colle.groups.all | print_manager | safe }} ({{ colle.volume }} / {{ colle.slot.capacity }})</li>
<li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li> <li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li>
<li><i class="fa-solid fa-circle-exclamation"></i> <li><i class="fa-solid fa-circle-exclamation"></i>

View File

@ -85,8 +85,8 @@ def dashboard(request):
colles_per_sem = [None] * len(term.range_weeks()) colles_per_sem = [None] * len(term.range_weeks())
for k, n in enumerate(term.range_weeks()): for k, n in enumerate(term.range_weeks()):
lundi = term.cls.week_beginning_date(n) lundi = term.cls.week_beginning_date(n)
colles_per_sem[k] = n, lundi, colles.filter(date__gte=max(lundi, date.today()), colles_per_sem[k] = n, lundi, colles.filter(datetime__gte=max(lundi, date.today()),
date__lt=lundi + timedelta(weeks=1)) datetime__lt=lundi + timedelta(weeks=1))
template = loader.get_template("dashboard.html") template = loader.get_template("dashboard.html")
calendar_link = get_calendar_link(student, term) calendar_link = get_calendar_link(student, term)
@ -179,7 +179,7 @@ def colloscope(request):
for sem in weeks: for sem in weeks:
lundi = term.cls.week_beginning_date(sem) lundi = term.cls.week_beginning_date(sem)
rot = Colle.objects.filter(slot=c, date__gte=lundi, date__lt=lundi + timedelta(weeks=1)) rot = Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1))
exists = rot.exists() exists = rot.exists()
if exists: if exists:

View File

@ -83,7 +83,14 @@ class ColleViewset(ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return Colle.objects.all() return (Colle.objects
.select_related("slot", "slot__term")
.prefetch_related("swap_set")
.annotate(base_vol=Count("groups__members"))
.annotate(swap_plus=Count("swap", filter=Q(swap__enroll=1), distinct=True))
.annotate(swap_minus=Count("swap", filter=Q(swap__enroll=0), distinct=True))
.annotate(volume=F("base_vol") + F("swap_plus") - F("swap_minus"))
.order_by("datetime", "slot__time"))
class CalendarLinkViewset(ReadOnlyModelViewSet): class CalendarLinkViewset(ReadOnlyModelViewSet):

View File

@ -16,6 +16,7 @@ Including another URLconf
""" """
from django.contrib import admin, auth from django.contrib import admin, auth
from django.urls import include, path from django.urls import include, path
from django.shortcuts import redirect
from django.contrib.staticfiles import views as vstatic from django.contrib.staticfiles import views as vstatic
from rest_framework import routers from rest_framework import routers
from rest_framework_simplejwt.views import ( from rest_framework_simplejwt.views import (
@ -47,7 +48,9 @@ urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/doc/', SpectacularSwaggerView.as_view(url_name='schema'), name='api-doc'), path('api/documentation/', SpectacularSwaggerView.as_view(url_name='schema'), name='api-doc'),
path("api/", lambda request: redirect("api-doc")),
path("api/doc/", lambda request: redirect("api-doc")),
path("api/", include(router.urls)), path("api/", include(router.urls)),
path("oauth2/", include('oauth2_provider.urls', namespace='oauth2_provider')), path("oauth2/", include('oauth2_provider.urls', namespace='oauth2_provider')),