Compare commits
15 Commits
d29d921222
...
1df7753a66
Author | SHA1 | Date |
---|---|---|
|
1df7753a66 | |
|
1be82d3bb0 | |
|
efb4a54c91 | |
|
fcf2f41022 | |
|
69d4141ffa | |
|
acb5eff36b | |
|
eb84355d17 | |
|
3554d4fbcc | |
|
a4f87f4e2d | |
|
df44d6a028 | |
|
c9b2f4a652 | |
|
c770867c2c | |
|
f381b4dd44 | |
|
92d4c851a3 | |
|
6e367cb9dd |
35
README.md
35
README.md
|
@ -1,2 +1,35 @@
|
||||||
# kholles-web
|
# Colloscope : Votre colloscope. En ligne.
|
||||||
|
|
||||||
|
Certifié le dernier colloscope dont vous aurez besoin. Avec ses fonctionalités de synchronisation, il reste
|
||||||
|
toujours à jour pour vous permettre d'aborder vos colles sereinement, si vous connaissez votre cours (apprentissage
|
||||||
|
du cours non fourni).
|
||||||
|
|
||||||
|
## Soyez le premier informé lors d'une modification
|
||||||
|
|
||||||
|
Lorsqu'une colle est modifiée, le modification se propage à l'ensemble des pages visibles par les utilisateurs.
|
||||||
|
Aucune excuse pour manquer sa colle.
|
||||||
|
|
||||||
|
## Échangez vos colles en toute confianc
|
||||||
|
|
||||||
|
Vous ne pouvez pas venir à une colle ? Aucun problème : il vous suffit de l'échanger !
|
||||||
|
Le *Marketplace* intégré vous donne la possibilité de récupérer des colles disponibles.
|
||||||
|
|
||||||
|
## Un système interopérable
|
||||||
|
|
||||||
|
Vous pouvez synchroniser vos colles avec votre application de calendrier favorite. Il lui suffit de supporter
|
||||||
|
les liens iCalendar. C'est le cas de l'application Calendrier sur iOS, de OneCalendar sur Android et de
|
||||||
|
Mozilla Thunderbird sur GNU/Linux et macOS (et Microsoft Windows).
|
||||||
|
|
||||||
|
|
||||||
|
## Pensé par un nerd, pour les nerds.
|
||||||
|
|
||||||
|
Un système complet d'API REST vous permet d'intégrer ce colloscope à vos programmes tiers.
|
||||||
|
|
||||||
|
|
||||||
|
## Libre, pour toujours.
|
||||||
|
|
||||||
|
Colloscope est distribué avec la licence GNU Affero GPL. Cette licence garantit vos quatre libertés, à savoir :
|
||||||
|
0. Exécutez le code librement ;
|
||||||
|
1. Modifiez le code librement ;
|
||||||
|
2. Distribuez le code librement ;
|
||||||
|
3. Distribuez librement des versions modifiées du code.
|
|
@ -1,4 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from colloscope.models import *
|
from colloscope.models import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,18 +32,59 @@ class ColleInline(admin.StackedInline):
|
||||||
|
|
||||||
@admin.register(Slot)
|
@admin.register(Slot)
|
||||||
class SlotAdmin(admin.ModelAdmin):
|
class SlotAdmin(admin.ModelAdmin):
|
||||||
list_display = ('subject', 'colleur', "term", 'view_day', "time", "duration")
|
list_display = ('subject', 'colleur', "term", 'get_day', "time", "duration")
|
||||||
list_filter = ("subject", "colleur", "term")
|
list_filter = ("subject", "colleur", "term")
|
||||||
inlines = [ColleInline]
|
inlines = [ColleInline]
|
||||||
|
|
||||||
def view_day(self, obj):
|
def get_day(self, obj):
|
||||||
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
return jours[obj.day]
|
return jours[obj.day]
|
||||||
|
|
||||||
view_day.short_description = 'Day'
|
get_day.short_description = _('Day')
|
||||||
|
|
||||||
|
|
||||||
|
class SwapInline(admin.StackedInline):
|
||||||
|
model = Swap
|
||||||
|
raw_id_fields = ("colle",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Colle)
|
||||||
|
class ColleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('get_subject', 'get_colleur', 'get_room', 'datetime',)
|
||||||
|
list_filter = ('slot',)
|
||||||
|
inlines = [SwapInline]
|
||||||
|
|
||||||
|
def get_subject(self, obj):
|
||||||
|
return obj.slot.subject
|
||||||
|
|
||||||
|
def get_colleur(self, obj):
|
||||||
|
return obj.slot.colleur
|
||||||
|
|
||||||
|
def get_room(self, obj):
|
||||||
|
return obj.slot.room
|
||||||
|
|
||||||
|
get_subject.short_description = _('Subject')
|
||||||
|
get_colleur.short_description = _('Colleur')
|
||||||
|
get_room.short_description = _('Room')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Swap)
|
||||||
|
class SwapAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('get_subject', 'get_colleur', 'get_datetime', 'enroll', 'student')
|
||||||
|
list_filter = ('enroll', 'student')
|
||||||
|
|
||||||
|
def get_subject(self, obj):
|
||||||
|
return obj.colle.slot.subject
|
||||||
|
get_subject.short_description = _('Subject')
|
||||||
|
|
||||||
|
def get_colleur(self, obj):
|
||||||
|
return obj.colle.slot.colleur
|
||||||
|
get_subject.short_description = _('Colleur')
|
||||||
|
|
||||||
|
def get_datetime(self, obj):
|
||||||
|
return obj.colle.datetime
|
||||||
|
get_subject.short_description = _('Heure')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Colle)
|
|
||||||
admin.site.register(Swap)
|
|
||||||
admin.site.register(Profile)
|
admin.site.register(Profile)
|
||||||
admin.site.register(CalendarLink)
|
admin.site.register(CalendarLink)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.db import models
|
||||||
from django.db.models import F, Q, Count, QuerySet, Subquery, OuterRef, Sum
|
from django.db.models import F, Q, Count, QuerySet, Subquery, OuterRef, Sum
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from discord import Webhook
|
from discord import Webhook
|
||||||
|
|
||||||
|
@ -383,6 +384,9 @@ class Colle(models.Model):
|
||||||
# func = async_to_sync(swap.notify)
|
# func = async_to_sync(swap.notify)
|
||||||
# func()
|
# func()
|
||||||
|
|
||||||
|
def week_number(self):
|
||||||
|
return self.slot.term.cls.week_number(self.datetime.date())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
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())}}}"
|
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())}}}"
|
||||||
|
|
||||||
|
@ -408,22 +412,7 @@ class Profile(models.Model):
|
||||||
colleur = models.ForeignKey(Colleur, null=True, blank=True, on_delete=models.SET_NULL)
|
colleur = models.ForeignKey(Colleur, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Profil {self.user} : {self.student} ; {self.colleur}"
|
return "Student" if self.student is not None else "Colleur"
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_request(request, preprocess=lambda query: query):
|
|
||||||
user = request.user
|
|
||||||
session = request.session
|
|
||||||
|
|
||||||
match session.get("profile"):
|
|
||||||
case "student":
|
|
||||||
profil = preprocess(Profile.objects.filter(user=user)).get()
|
|
||||||
return profil.student
|
|
||||||
case "colleur":
|
|
||||||
profil = preprocess(Profile.objects.filter(user=user)).get()
|
|
||||||
return profil.colleur
|
|
||||||
case _:
|
|
||||||
raise ValueError("profil non choisi")
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarLink(models.Model):
|
class CalendarLink(models.Model):
|
||||||
|
|
|
@ -110,12 +110,10 @@ def generate(term):
|
||||||
|
|
||||||
def handle(request):
|
def handle(request):
|
||||||
try:
|
try:
|
||||||
student = Profile.from_request(
|
student = (Student.objects
|
||||||
request,
|
.select_related("cls")
|
||||||
preprocess=lambda query: query \
|
.prefetch_related("cls__term_set")
|
||||||
.select_related("student__cls") \
|
.get(profile__user=request.user))
|
||||||
.prefetch_related("student__cls__term_set")
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.select_profile")
|
return redirect("colloscope.select_profile")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load extras %}
|
||||||
|
|
||||||
|
{% block title %}Tableau de bord{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
{% block intro %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% if colles %}
|
||||||
|
{% regroup colles by week_number as week_list %}
|
||||||
|
{% for week in week_list %}
|
||||||
|
|
||||||
|
<h3 class="week" id="week-no-{{ week.grouper }}">
|
||||||
|
<a href="#week-no-{{ week.grouper }}">Semaine {{ week.grouper }}</a>
|
||||||
|
</h3>
|
||||||
|
<div class="colle-wrapper">
|
||||||
|
{% for colle in week.list %}
|
||||||
|
<div class="colle">
|
||||||
|
<ul>
|
||||||
|
<li><i class="fa-solid fa-graduation-cap"></i> {{ colle.slot.subject }}</li>
|
||||||
|
<li><i class="fa-solid fa-person-chalkboard"></i> {{ colle.slot.colleur }}</li>
|
||||||
|
<li><i class="fa-solid fa-clock"></i> {{ colle.datetime|date:"l"|title }} {{ colle.datetime|date:"DATETIME_FORMAT" }}</li>
|
||||||
|
<li>
|
||||||
|
<details>
|
||||||
|
<summary><i class="fa-solid fa-users"></i>
|
||||||
|
{{ colle.groups.all | print_manager | safe }}
|
||||||
|
({{ colle.volume }} / {{ colle.slot.capacity }})</summary>
|
||||||
|
{% if colle.final_group.exists %}
|
||||||
|
<ul>
|
||||||
|
{% for member in colle.final_group.all %}
|
||||||
|
<li>{{ member }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
Personne n'est inscrit à cette colle.
|
||||||
|
{% endif %}
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
<li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li>
|
||||||
|
{% if False %}
|
||||||
|
|
||||||
|
<li><i class="fa-solid fa-circle-exclamation"></i>
|
||||||
|
<form
|
||||||
|
action="{% url "colloscope:withdraw" %}"
|
||||||
|
method="POST"
|
||||||
|
onsubmit="return confirm('Êtes-vous sûr de vouloir vous désinscrire de la colle {{ colle }} ');">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="colle_id" value="{{ colle.id }}">
|
||||||
|
<button type="submit">Rendre disponible</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
Aucune colle.
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock main %}
|
|
@ -13,30 +13,33 @@
|
||||||
Bienvenue {{ student }}. Votre lycée est {{ term.cls.school.description }}, et votre classe est {{ term.cls.description }}.
|
Bienvenue {{ student }}. Votre lycée est {{ term.cls.school.description }}, et votre classe est {{ term.cls.description }}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>Période actuelle : {{ term }}. Votre groupe de colle est {{ group }}. <a href="table.html">Consulter le colloscope</a></p>
|
<p>Période actuelle : {{ term }}. Votre groupe de colle est {{ group }}. <a href="{% url "colloscope:table" %}">Consulter le colloscope</a></p>
|
||||||
|
|
||||||
<h2>Mes colles</h2>
|
<h2>Mes colles</h2>
|
||||||
|
|
||||||
<p><a href="{{ calendar_link }}"><i class="fa-regular fa-calendar"></i> Exporter en .ics (ceci est un permalien public)</a></p>
|
<p><a href="{{ calendar_link }}"><i class="fa-regular fa-calendar"></i> Exporter en .ics (ceci est un permalien public)</a></p>
|
||||||
|
|
||||||
<p><a href="{% url "colloscope.marketplace" %}">Accéder au marketplace</a></p>
|
<p><a href="{% url "colloscope:marketplace" %}">Accéder au marketplace</a></p>
|
||||||
|
|
||||||
|
|
||||||
{% for n, lundi, colles in colles_per_sem %}
|
{% for n, lundi, colles in colles_per_sem %}
|
||||||
|
|
||||||
{% if colles %}
|
{% if colles %}
|
||||||
<h3 class="week">Semaine {{n}} ({{lundi}})</h3>
|
<h3 class="week" id="week-no-{{ n }}">
|
||||||
|
<a href="#week-no-{{ n }}">Semaine {{n}} ({{lundi}})</a>
|
||||||
|
</h3>
|
||||||
<div class="colle-wrapper">
|
<div class="colle-wrapper">
|
||||||
{% for colle in colles %}
|
{% for colle in colles %}
|
||||||
<div class="colle">
|
<div class="colle">
|
||||||
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
|
|
||||||
<ul>
|
<ul>
|
||||||
<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-graduation-cap"></i> {{ colle.slot.subject }}</li>
|
||||||
|
<li><i class="fa-solid fa-person-chalkboard"></i> {{ colle.slot.colleur }}</li>
|
||||||
|
<li><i class="fa-solid fa-clock"></i> {{ colle.datetime|date:"l"|title }} {{ 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>
|
||||||
<form
|
<form
|
||||||
action="{% url "colloscope.withdraw" %}"
|
action="{% url "colloscope:withdraw" %}"
|
||||||
method="POST"
|
method="POST"
|
||||||
onsubmit="return confirm('Êtes-vous sûr de vouloir vous désinscrire de la colle {{ colle }} ');">
|
onsubmit="return confirm('Êtes-vous sûr de vouloir vous désinscrire de la colle {{ colle }} ');">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
<div class="colle-wrapper">
|
<div class="colle-wrapper">
|
||||||
{% for colle in colles %}
|
{% for colle in colles %}
|
||||||
<div class="colle">
|
<div class="colle">
|
||||||
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
|
|
||||||
<ul>
|
<ul>
|
||||||
<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-graduation-cap"></i> {{ colle.slot.subject }}</li>
|
||||||
|
<li><i class="fa-solid fa-person-chalkboard"></i> {{ colle.slot.colleur }}</li>
|
||||||
|
<li><i class="fa-solid fa-clock"></i> {{ colle.datetime|date:"l"|title }} {{ 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>
|
||||||
<form action="{% url "colloscope.enroll" %}"
|
<form action="{% url "colloscope:enroll" %}"
|
||||||
method="POST"
|
method="POST"
|
||||||
onsubmit="return confirm('Êtes-vous sûr de vouloir vous inscrire à la colle {{ colle }} ');">
|
onsubmit="return confirm('Êtes-vous sûr de vouloir vous inscrire à la colle {{ colle }} ');">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
|
@ -13,12 +13,12 @@
|
||||||
<h1>Colloscope</h1>
|
<h1>Colloscope</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Lycée : {{ term.cls.school.description }}. Classe : {{ term.cls.description }}. <a href="dashboard.html">Retour au tableau de bord</a>
|
Lycée : {{ term.cls.school.description }}. Classe : {{ term.cls.description }}. <a href="{% url "colloscope:table" %}">Retour au tableau de bord</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Colloscope : {{ term.description }}</h2>
|
<h2>Colloscope : {{ term.description }}</h2>
|
||||||
|
|
||||||
<form method="get" action="{% url "colloscope.table" %}">
|
<form method="get" action="{% url "colloscope:table" %}">
|
||||||
Changer de période :
|
Changer de période :
|
||||||
<select name="term" id="term">
|
<select name="term" id="term">
|
||||||
{% for p in term.cls.term_set.all %}
|
{% for p in term.cls.term_set.all %}
|
||||||
|
@ -33,9 +33,9 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if request.GET.term %}
|
{% if request.GET.term %}
|
||||||
<a href="export.pdf?term={{ request.GET.term }}" target="_blank">Exporter le colloscope</a>
|
<a href="{% url 'colloscope:export' %}?term={{ request.GET.term }}" target="_blank">Exporter le colloscope</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="export.pdf" target="_blank">Exporter le colloscope</a>
|
<a href="{% url 'colloscope:export' %}" target="_blank">Exporter le colloscope</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from django.shortcuts import redirect
|
||||||
from . import views
|
from . import views
|
||||||
|
from .views import ColleListView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.home_redirect, name="colloscope.home"),
|
path("", lambda req: redirect("colloscope:dashboard"), name="home"),
|
||||||
path("table.html", views.colloscope, name="colloscope.table"),
|
path("table/", views.colloscope, name="table"),
|
||||||
path("dashboard.html", views.dashboard, name="colloscope.dashboard"),
|
path("dashboard/", views.dashboard, name="dashboard"),
|
||||||
path("export.pdf", views.export, name="colloscope.export"),
|
path("export.pdf", views.export, name="export"),
|
||||||
path("export/calendar/<str:key>/calendar.ics", views.icalendar, name="colloscope.calendar.ics"),
|
path("export/calendar/<str:key>/calendar.ics", views.icalendar, name="export-ics"),
|
||||||
path("select_profile", views.select_profile, name="colloscope.select_profile"),
|
path("calendrier.ics",
|
||||||
path("marketplace.html", views.marketplace, name="colloscope.marketplace"),
|
lambda req: redirect("colloscope:export-ics", key=req.GET.get("key")), name="export-ics-old"),
|
||||||
path("action/enroll", views.enroll, name="colloscope.enroll"),
|
path("marketplace/", views.marketplace, name="marketplace"),
|
||||||
path("action/withdraw", views.withdraw, name="colloscope.withdraw"),
|
path("action/enroll/", views.enroll, name="enroll"),
|
||||||
|
path("action/withdraw/", views.withdraw, name="withdraw"),
|
||||||
|
path("listing/<int:term>/", ColleListView.as_view(), name="colles"),
|
||||||
|
path("listing/<int:term>/by_subject/<int:subject>/", ColleListView.as_view(), name="colles_by_subject"),
|
||||||
|
path("listing/<int:term>/by_colleur/<int:colleur>/", ColleListView.as_view(), name="colles_by_colleur"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from colloscope.models import *
|
from colloscope.models import *
|
||||||
from colloscope.pdfexport import handle
|
from colloscope.pdfexport import handle
|
||||||
|
@ -13,66 +16,41 @@ from colloscope.icalexport import to_calendar
|
||||||
|
|
||||||
|
|
||||||
def handler404(request):
|
def handler404(request):
|
||||||
template = loader.get_template("404.html")
|
|
||||||
context = {}
|
context = {}
|
||||||
return HttpResponse(template.render(context), status=404)
|
return render(request, '404.html', context, status=404)
|
||||||
|
|
||||||
|
|
||||||
def home_redirect(request):
|
class ColleListView(ListView):
|
||||||
return redirect("/colloscope/dashboard.html")
|
model = Colle
|
||||||
|
context_object_name = "colles"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
term = Term.objects.get(pk=self.kwargs.get("term"))
|
||||||
|
base_query = (term.cls
|
||||||
|
.current_term()
|
||||||
|
.query_colles()
|
||||||
|
.filter(datetime__gte=date.today()))
|
||||||
|
|
||||||
@login_required
|
if self.kwargs.get("subject") is not None:
|
||||||
def select_profile(request):
|
print(base_query)
|
||||||
user = request.user
|
print(self.kwargs.get("subject"))
|
||||||
session = request.session
|
base_query = base_query.filter(slot__subject__id=self.kwargs.get("subject"))
|
||||||
|
if self.kwargs.get("colleur") is not None:
|
||||||
|
base_query = base_query.filter(slot__colleur__id=self.kwargs.get("colleur"))
|
||||||
|
|
||||||
if not Profile.objects.filter(user=user).exists():
|
return base_query
|
||||||
profile = Profile(user=user)
|
|
||||||
profile.save()
|
|
||||||
else:
|
|
||||||
profile = Profile.objects.get(user=user)
|
|
||||||
|
|
||||||
if profile.student is not None and profile.colleur is None:
|
|
||||||
session["profile"] = "student"
|
|
||||||
return redirect("/colloscope/")
|
|
||||||
elif profile.colleur is not None and profile.student is None:
|
|
||||||
session["profile"] = "colleur"
|
|
||||||
return redirect("/colloscope/")
|
|
||||||
else:
|
|
||||||
if profile.student is not None:
|
|
||||||
template = loader.get_template("select_profile.html")
|
|
||||||
else:
|
|
||||||
template = loader.get_template("unbound_profile.html")
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"profile": profile,
|
|
||||||
}
|
|
||||||
return HttpResponse(template.render(context))
|
|
||||||
|
|
||||||
|
|
||||||
def get_lien_calendrier(student, term):
|
|
||||||
try:
|
|
||||||
lien = CalendarLink.objects.get(student=student, term=term)
|
|
||||||
except CalendarLink.DoesNotExist:
|
|
||||||
key = uuid4().hex
|
|
||||||
lien = CalendarLink(key=key, student=student, term=term)
|
|
||||||
lien.save()
|
|
||||||
|
|
||||||
return f"calendrier.ics?key={lien.key}"
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
try:
|
try:
|
||||||
student = Profile.from_request(
|
student = (Student.objects
|
||||||
request,
|
.select_related("cls")
|
||||||
preprocess=lambda query: (query
|
.prefetch_related("cls__term_set")
|
||||||
.select_related("student__cls")
|
.get(profile__user=request.user))
|
||||||
.prefetch_related("student__cls__term_set"))
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.select_profile")
|
return redirect("colloscope:select_profile")
|
||||||
|
|
||||||
if not isinstance(student, Student):
|
if not isinstance(student, Student):
|
||||||
return HttpResponse("pas encore supporté")
|
return HttpResponse("pas encore supporté")
|
||||||
|
@ -117,17 +95,15 @@ class WithdrawForm(AmendForm):
|
||||||
@login_required
|
@login_required
|
||||||
def marketplace(request):
|
def marketplace(request):
|
||||||
try:
|
try:
|
||||||
student = Profile.from_request(
|
student = (Student.objects
|
||||||
request,
|
.select_related("cls")
|
||||||
preprocess=lambda query: (query
|
.prefetch_related("cls__term_set")
|
||||||
.select_related("student__cls")
|
.get(profile__user=request.user))
|
||||||
.prefetch_related("student__cls__term_set"))
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.select_profile")
|
return redirect("colloscope:select_profile")
|
||||||
|
|
||||||
if not isinstance(student, Student):
|
if not isinstance(student, Student):
|
||||||
return HttpResponse("pas encore supporté")
|
return HttpResponse(_("Not supported yet."))
|
||||||
|
|
||||||
term = student.cls.current_term()
|
term = student.cls.current_term()
|
||||||
colles = term.query_colles_not_full_excluding_student(student)
|
colles = term.query_colles_not_full_excluding_student(student)
|
||||||
|
@ -142,14 +118,12 @@ def marketplace(request):
|
||||||
@login_required
|
@login_required
|
||||||
def colloscope(request):
|
def colloscope(request):
|
||||||
try:
|
try:
|
||||||
student = Profile.from_request(
|
student = (Student.objects
|
||||||
request,
|
.select_related("cls")
|
||||||
preprocess=lambda query: (query
|
.prefetch_related("cls__term_set")
|
||||||
.select_related("student__cls")
|
.get(profile__user=request.user))
|
||||||
.prefetch_related("student__cls__term_set"))
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.select_profile")
|
return redirect("colloscope:select_profile")
|
||||||
|
|
||||||
if not isinstance(student, Student):
|
if not isinstance(student, Student):
|
||||||
return HttpResponse("pas encore supporté")
|
return HttpResponse("pas encore supporté")
|
||||||
|
@ -237,12 +211,10 @@ def icalendar(request, key):
|
||||||
|
|
||||||
def amend(request, colle_id, do_enroll):
|
def amend(request, colle_id, do_enroll):
|
||||||
try:
|
try:
|
||||||
student = Profile.from_request(
|
student = (Student.objects
|
||||||
request,
|
.select_related("cls")
|
||||||
preprocess=lambda query: (query
|
.prefetch_related("cls__term_set")
|
||||||
.select_related("student__cls")
|
.get(profile__user=request.user))
|
||||||
.prefetch_related("student__cls__term_set"))
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.choix_profil")
|
return redirect("colloscope.choix_profil")
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ INSTALLED_APPS = [
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
'rest_framework_simplejwt',
|
'rest_framework_simplejwt',
|
||||||
'colloscope',
|
'colloscope',
|
||||||
|
"front",
|
||||||
"drf_spectacular",
|
"drf_spectacular",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -124,6 +125,7 @@ LANGUAGE_CODE = 'fr'
|
||||||
TIME_ZONE = 'Europe/Paris'
|
TIME_ZONE = 'Europe/Paris'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
@ -156,8 +158,8 @@ STATICFILES_FINDERS = [
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
LOGIN_URL = "/accounts/login"
|
LOGIN_URL = "/accounts/login"
|
||||||
LOGIN_REDIRECT_URL = "home"
|
LOGIN_REDIRECT_URL = "colloscope:dashboard"
|
||||||
LOGOUT_REDIRECT_URL = "home"
|
LOGOUT_REDIRECT_URL = "front:index"
|
||||||
|
|
||||||
DISCORD_NOTIFY_WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_URL"
|
DISCORD_NOTIFY_WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_URL"
|
||||||
DISCORD_NOTIFY_WEBHOOK_USERNAME = "Watchdog"
|
DISCORD_NOTIFY_WEBHOOK_USERNAME = "Watchdog"
|
||||||
|
|
|
@ -16,16 +16,15 @@ 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.shortcuts import redirect, render
|
||||||
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 (
|
||||||
TokenObtainPairView,
|
TokenObtainPairView,
|
||||||
TokenRefreshView,
|
TokenRefreshView,
|
||||||
)
|
)
|
||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||||
|
|
||||||
from colloscope.views import home_redirect
|
|
||||||
from colloscope.viewsets import *
|
from colloscope.viewsets import *
|
||||||
|
|
||||||
router = routers.SimpleRouter()
|
router = routers.SimpleRouter()
|
||||||
|
@ -43,7 +42,9 @@ router.register("calendarlink", CalendarLinkViewset, basename='calendarlink')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', home_redirect, name="home"),
|
path('', lambda req: render(req, "index.html", {}), name="home"),
|
||||||
|
|
||||||
|
path("__debug__/", include("debug_toolbar.urls")),
|
||||||
path('api-auth/', include('rest_framework.urls')),
|
path('api-auth/', include('rest_framework.urls')),
|
||||||
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'),
|
||||||
|
@ -55,7 +56,7 @@ urlpatterns = [
|
||||||
|
|
||||||
path("oauth2/", include('oauth2_provider.urls', namespace='oauth2_provider')),
|
path("oauth2/", include('oauth2_provider.urls', namespace='oauth2_provider')),
|
||||||
path("favicon.ico", lambda req: vstatic.serve(req, "favicon.ico")),
|
path("favicon.ico", lambda req: vstatic.serve(req, "favicon.ico")),
|
||||||
path('colloscope/', include('colloscope.urls')),
|
path('colloscope/', include(('colloscope.urls', "colloscope"), namespace="colloscope")),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('accounts/', include("django.contrib.auth.urls")),
|
path('accounts/', include("django.contrib.auth.urls")),
|
||||||
]
|
]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -14,6 +14,7 @@ defusedxml==0.7.1
|
||||||
discord.py==2.3.2
|
discord.py==2.3.2
|
||||||
Django==5.0.4
|
Django==5.0.4
|
||||||
django-cors-headers==4.3.1
|
django-cors-headers==4.3.1
|
||||||
|
django-debug-toolbar==4.3.0
|
||||||
django-oauth-toolkit==2.3.0
|
django-oauth-toolkit==2.3.0
|
||||||
django-smtp-ssl==1.0
|
django-smtp-ssl==1.0
|
||||||
djangorestframework==3.15.1
|
djangorestframework==3.15.1
|
||||||
|
|
|
@ -7,6 +7,15 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link:active, a:visited:active {
|
||||||
|
color: darkblue;
|
||||||
|
}
|
||||||
|
|
||||||
header .bandeau {
|
header .bandeau {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
@ -79,7 +88,7 @@ header .bandeau button:active {
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: 20px auto;
|
margin: 0 auto;
|
||||||
width: clamp(350px, 60%, 1200px);
|
width: clamp(350px, 60%, 1200px);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -129,7 +138,11 @@ nav.semaine .select, nav.semaine .label {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.semaine .select { background-color: dodgerblue; color: white; width: 1em; }
|
nav.semaine .select {
|
||||||
|
background-color: dodgerblue;
|
||||||
|
color: white;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
nav.semaine .select:hover { background-color: #0077ea; }
|
nav.semaine .select:hover { background-color: #0077ea; }
|
||||||
|
|
||||||
nav.semaine .label:hover {
|
nav.semaine .label:hover {
|
||||||
|
@ -144,13 +157,26 @@ p.programme {
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.week {
|
.week {
|
||||||
background-color: dodgerblue;
|
background-color: dodgerblue;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 5px;
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
transition: background-color 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week a, .week a:hover, .week a:active {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week:hover {
|
||||||
|
background-color: #0077ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.week.empty {
|
.week.empty {
|
||||||
|
@ -159,7 +185,7 @@ footer {
|
||||||
|
|
||||||
.colle-wrapper {
|
.colle-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 400px)
|
@media screen and (min-width: 400px)
|
||||||
|
@ -170,8 +196,15 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
.colle {
|
.colle {
|
||||||
border: 1px solid black;
|
padding: 15px;
|
||||||
padding: 10px;
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 3px 6px rgba(0,0,0,0.04),0 3px 6px rgba(0,0,0,0.0575);
|
||||||
|
|
||||||
|
transition: background-color 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle:hover {
|
||||||
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colle span {
|
.colle span {
|
||||||
|
@ -180,6 +213,7 @@ footer {
|
||||||
|
|
||||||
.colle ul {
|
.colle ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colle li {
|
.colle li {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9e2427d5ae8e7fafef9fe001394e9566e181f217
|
Subproject commit 811b224d6a8a644dffd6c34cb54acbbd1a0f4db0
|
|
@ -32,20 +32,27 @@
|
||||||
|
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
<a href="{% url "home" %}">
|
||||||
|
<div class="link"><i class="fa-solid fa-home"></i> Accueil</div>
|
||||||
|
</a>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<a href="{% url "colloscope.dashboard" %}">
|
<a href="{% url "colloscope:dashboard" %}">
|
||||||
<div class="link"><i class="fa-solid fa-rocket"></i> Tableau de bord</div>
|
<div class="link"><i class="fa-solid fa-rocket"></i> Tableau de bord</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url "colloscope.table" %}">
|
<a href="{% url "colloscope:table" %}">
|
||||||
<div class="link"><i class="fa-solid fa-calendar"></i> Colloscope</div>
|
<div class="link"><i class="fa-solid fa-calendar"></i> Colloscope</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url "colloscope.marketplace" %}">
|
<a href="{% url "colloscope:marketplace" %}">
|
||||||
<div class="link"><i class="fa-solid fa-shop"></i> Marketplace</div>
|
<div class="link"><i class="fa-solid fa-shop"></i> Marketplace</div>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
<div class="link">
|
||||||
|
<i class="fa-solid fa-user"></i> {{ user.username }} ({{ request.user.profile }})
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{% url 'logout' %}" method="post">
|
<form action="{% url 'logout' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="link" type="submit" href="{% url "login" %}">
|
<button class="link" type="submit" href="{% url "login" %}">
|
||||||
|
@ -66,6 +73,6 @@
|
||||||
{% block main %}{% endblock main %}
|
{% block main %}{% endblock main %}
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
{% block footer %}© colles.mp2i-vms.fr 2024 - <a href="https://git.mp2i-vms.fr/mp2i-vms/kholles-web" target="_blank">Code source</a> - <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">Licence GNU AGPL v3 or later</a>{% endblock footer %}
|
{% block footer %}© colles.mp2i-vms.fr 2024 - <a href="https://git.mp2i-vms.fr/mp2i-vms/colles.mp2i-vms.fr" target="_blank">Code source</a> - <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">Licence GNU AGPL v3 or later</a>{% endblock footer %}
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}Accueil{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>{% translate "Your colloscope. Online." %}</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Certifié le dernier colloscope dont vous aurez besoin. Avec ses fonctionalités de synchronisation, il reste
|
||||||
|
toujours à jour pour vous permettre d'aborder vos colles sereinement, si vous connaissez votre cours (apprentissage
|
||||||
|
du cours non fourni).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Soyez le premier informé lors d'une modification</h2>
|
||||||
|
<p>
|
||||||
|
Lorsqu'une colle est modifiée, le modification se propage à l'ensemble des pages visibles par les utilisateurs.
|
||||||
|
Aucune excuse pour manquer sa colle.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Échangez vos colles en toute confiance</h2>
|
||||||
|
<p>
|
||||||
|
Vous ne pouvez pas venir à une colle ? Aucun problème : il vous suffit de l'échanger !
|
||||||
|
Le <em>Marketplace</em> intégré vous donne la possibilité de récupérer des colles disponibles.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Un système interopérable</h2>
|
||||||
|
<p>
|
||||||
|
Vous pouvez synchroniser vos colles avec votre application de calendrier favorite. Il lui suffit de supporter
|
||||||
|
les liens iCalendar. C'est le cas de l'application Calendrier sur iOS, de OneCalendar sur Android et de
|
||||||
|
Mozilla Thunderbird sur GNU/Linux et macOS (et Microsoft Windows).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Pensé par un nerd, pour les nerds.</h2>
|
||||||
|
<p>
|
||||||
|
Un système complet d'API REST vous permet d'intégrer ce colloscope à vos programmes tiers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Libre, pour toujours.</h2>
|
||||||
|
<p>
|
||||||
|
Colloscope est distribué avec la licence GNU Affero GPL. Cette licence garantit vos quatre libertés, à savoir :
|
||||||
|
</p>
|
||||||
|
<ol start=0>
|
||||||
|
<li>Exécutez le code librement ;</li>
|
||||||
|
<li>Modifiez le code librement ;</li>
|
||||||
|
<li>Distribuez le code librement ;</li>
|
||||||
|
<li>Distribuez librement des versions modifiées du code.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue