Compare commits

...

16 Commits
main ... dev

18 changed files with 350 additions and 2599 deletions

View File

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

View File

@ -1,4 +1,5 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from colloscope.models import *
@ -31,18 +32,59 @@ class ColleInline(admin.StackedInline):
@admin.register(Slot)
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")
inlines = [ColleInline]
def view_day(self, obj):
def get_day(self, obj):
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
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(CalendarLink)

View File

@ -9,6 +9,7 @@ from django.db import models
from django.db.models import F, Q, Count, QuerySet, Subquery, OuterRef, Sum
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from discord import Webhook
@ -385,6 +386,9 @@ class Colle(models.Model):
# func = async_to_sync(swap.notify)
# func()
def week_number(self):
return self.slot.term.cls.week_number(self.datetime.date())
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())}}}"
@ -410,22 +414,7 @@ class Profile(models.Model):
colleur = models.ForeignKey(Colleur, null=True, blank=True, on_delete=models.SET_NULL)
def __str__(self):
return f"Profil {self.user} : {self.student} ; {self.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")
return "Student" if self.student is not None else "Colleur"
class CalendarLink(models.Model):

View File

@ -110,12 +110,10 @@ def generate(term):
def handle(request):
try:
student = Profile.from_request(
request,
preprocess=lambda query: query \
.select_related("student__cls") \
.prefetch_related("student__cls__term_set")
)
student = (Student.objects
.select_related("cls")
.prefetch_related("cls__term_set")
.get(profile__user=request.user))
except ValueError:
return redirect("colloscope.select_profile")

View File

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

View File

@ -13,30 +13,33 @@
Bienvenue {{ student }}. Votre lycée est {{ term.cls.school.description }}, et votre classe est {{ term.cls.description }}.
</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>
<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 %}
{% 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">
{% for colle in colles %}
<div class="colle">
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
<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-earth-americas"></i> {{ colle.slot.room }}</li>
<li><i class="fa-solid fa-circle-exclamation"></i>
<form
action="{% url "colloscope.withdraw" %}"
action="{% url "colloscope:withdraw" %}"
method="POST"
onsubmit="return confirm('Êtes-vous sûr de vouloir vous désinscrire de la colle {{ colle }} ');">
{% csrf_token %}

View File

@ -15,13 +15,14 @@
<div class="colle-wrapper">
{% for colle in colles %}
<div class="colle">
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
<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-earth-americas"></i> {{ colle.slot.room }}</li>
<li><i class="fa-solid fa-circle-exclamation"></i>
<form action="{% url "colloscope.enroll" %}"
<form action="{% url "colloscope:enroll" %}"
method="POST"
onsubmit="return confirm('Êtes-vous sûr de vouloir vous inscrire à la colle {{ colle }} ');">
{% csrf_token %}

View File

@ -13,12 +13,12 @@
<h1>Colloscope</h1>
<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>
<h2>Colloscope : {{ term.description }}</h2>
<form method="get" action="{% url "colloscope.table" %}">
<form method="get" action="{% url "colloscope:table" %}">
Changer de période&nbsp;:
<select name="term" id="term">
{% for p in term.cls.term_set.all %}
@ -33,9 +33,9 @@
</form>
{% 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 %}
<a href="export.pdf" target="_blank">Exporter le colloscope</a>
<a href="{% url 'colloscope:export' %}" target="_blank">Exporter le colloscope</a>
{% endif %}
<div class="table-wrapper">

View File

@ -1,14 +1,20 @@
from django.urls import path
from django.shortcuts import redirect
from . import views
from .views import ColleListView
urlpatterns = [
path("", views.home_redirect, name="colloscope.home"),
path("table.html", views.colloscope, name="colloscope.table"),
path("dashboard.html", views.dashboard, name="colloscope.dashboard"),
path("export.pdf", views.export, name="colloscope.export"),
path("export/calendar/<str:key>/calendar.ics", views.icalendar, name="colloscope.calendar.ics"),
path("select_profile", views.select_profile, name="colloscope.select_profile"),
path("marketplace.html", views.marketplace, name="colloscope.marketplace"),
path("action/enroll", views.enroll, name="colloscope.enroll"),
path("action/withdraw", views.withdraw, name="colloscope.withdraw"),
path("", lambda req: redirect("colloscope:dashboard"), name="home"),
path("table/", views.colloscope, name="table"),
path("dashboard/", views.dashboard, name="dashboard"),
path("export.pdf", views.export, name="export"),
path("export/calendar/<str:key>/calendar.ics", views.icalendar, name="export-ics"),
path("calendrier.ics",
lambda req: redirect("colloscope:export-ics", key=req.GET.get("key")), name="export-ics-old"),
path("marketplace/", views.marketplace, name="marketplace"),
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"),
]

View File

@ -1,11 +1,14 @@
from uuid import uuid4
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect, render
from django.http import HttpResponse, HttpResponseRedirect
from django.template import loader
from django.views.decorators.http import require_POST
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.pdfexport import handle
@ -13,66 +16,41 @@ from colloscope.icalexport import to_calendar
def handler404(request):
template = loader.get_template("404.html")
context = {}
return HttpResponse(template.render(context), status=404)
return render(request, '404.html', context, status=404)
def home_redirect(request):
return redirect("/colloscope/dashboard.html")
class ColleListView(ListView):
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
def select_profile(request):
user = request.user
session = request.session
if self.kwargs.get("subject") is not None:
print(base_query)
print(self.kwargs.get("subject"))
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():
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}"
return base_query
@login_required
def dashboard(request):
try:
student = Profile.from_request(
request,
preprocess=lambda query: (query
.select_related("student__cls")
.prefetch_related("student__cls__term_set"))
)
student = (Student.objects
.select_related("cls")
.prefetch_related("cls__term_set")
.get(profile__user=request.user))
except ValueError:
return redirect("colloscope.select_profile")
return redirect("colloscope:select_profile")
if not isinstance(student, Student):
return HttpResponse("pas encore supporté")
@ -117,17 +95,15 @@ class WithdrawForm(AmendForm):
@login_required
def marketplace(request):
try:
student = Profile.from_request(
request,
preprocess=lambda query: (query
.select_related("student__cls")
.prefetch_related("student__cls__term_set"))
)
student = (Student.objects
.select_related("cls")
.prefetch_related("cls__term_set")
.get(profile__user=request.user))
except ValueError:
return redirect("colloscope.select_profile")
return redirect("colloscope:select_profile")
if not isinstance(student, Student):
return HttpResponse("pas encore supporté")
return HttpResponse(_("Not supported yet."))
term = student.cls.current_term()
colles = term.query_colles_not_full_excluding_student(student)
@ -142,14 +118,12 @@ def marketplace(request):
@login_required
def colloscope(request):
try:
student = Profile.from_request(
request,
preprocess=lambda query: (query
.select_related("student__cls")
.prefetch_related("student__cls__term_set"))
)
student = (Student.objects
.select_related("cls")
.prefetch_related("cls__term_set")
.get(profile__user=request.user))
except ValueError:
return redirect("colloscope.select_profile")
return redirect("colloscope:select_profile")
if not isinstance(student, Student):
return HttpResponse("pas encore supporté")
@ -217,7 +191,7 @@ def get_calendar_link(student, term):
lien = CalendarLink(key=key, student=student, term=term)
lien.save()
return f"export/calendar/{lien.key}/calendar.ics"
return f"/colloscope/export/calendar/{lien.key}/calendar.ics"
def icalendar(request, key):
@ -237,12 +211,10 @@ def icalendar(request, key):
def amend(request, colle_id, do_enroll):
try:
student = Profile.from_request(
request,
preprocess=lambda query: (query
.select_related("student__cls")
.prefetch_related("student__cls__term_set"))
)
student = (Student.objects
.select_related("cls")
.prefetch_related("cls__term_set")
.get(profile__user=request.user))
except ValueError:
return redirect("colloscope.choix_profil")

View File

@ -52,6 +52,7 @@ INSTALLED_APPS = [
"rest_framework",
'rest_framework_simplejwt',
'colloscope',
"front",
"drf_spectacular",
]
@ -124,6 +125,7 @@ LANGUAGE_CODE = 'fr'
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
USE_L10N = True
USE_TZ = True
@ -156,8 +158,8 @@ STATICFILES_FINDERS = [
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = "/accounts/login"
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"
LOGIN_REDIRECT_URL = "colloscope:dashboard"
LOGOUT_REDIRECT_URL = "front:index"
DISCORD_NOTIFY_WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_URL"
DISCORD_NOTIFY_WEBHOOK_USERNAME = "Watchdog"

View File

@ -16,16 +16,15 @@ Including another URLconf
"""
from django.contrib import admin, auth
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 rest_framework import routers
from rest_framework_simplejwt.views import (
TokenObtainPairView,
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 *
router = routers.SimpleRouter()
@ -43,7 +42,9 @@ router.register("calendarlink", CalendarLinkViewset, basename='calendarlink')
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/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
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("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('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

View File

@ -14,6 +14,7 @@ defusedxml==0.7.1
discord.py==2.3.2
Django==5.0.4
django-cors-headers==4.3.1
django-debug-toolbar==4.3.0
django-oauth-toolkit==2.3.0
django-smtp-ssl==1.0
djangorestframework==3.15.1

View File

@ -7,6 +7,15 @@ body {
margin: 0;
}
a:link, a:visited {
color: blue;
text-decoration: underline;
}
a:link:active, a:visited:active {
color: darkblue;
}
header .bandeau {
display: block;
background-color: #333;
@ -79,7 +88,7 @@ header .bandeau button:active {
}
main {
margin: 20px auto;
margin: 0 auto;
width: clamp(350px, 60%, 1200px);
background-color: white;
padding: 10px;
@ -129,7 +138,11 @@ nav.semaine .select, nav.semaine .label {
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 .label:hover {
@ -144,13 +157,26 @@ p.programme {
footer {
text-align: center;
margin: 20px 0;
}
.week {
background-color: dodgerblue;
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 {
@ -159,7 +185,7 @@ footer {
.colle-wrapper {
display: grid;
gap: 10px;
gap: 15px;
}
@media screen and (min-width: 400px)
@ -170,8 +196,15 @@ footer {
}
.colle {
border: 1px solid black;
padding: 10px;
padding: 15px;
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 {
@ -180,6 +213,7 @@ footer {
.colle ul {
padding: 0;
margin: 0;
}
.colle li {

View File

@ -32,20 +32,27 @@
<div class="navbar">
<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 %}
<a href="{% url "colloscope.dashboard" %}">
<a href="{% url "colloscope:dashboard" %}">
<div class="link"><i class="fa-solid fa-rocket"></i> Tableau de bord</div>
</a>
<a href="{% url "colloscope.table" %}">
<a href="{% url "colloscope:table" %}">
<div class="link"><i class="fa-solid fa-calendar"></i> Colloscope</div>
</a>
<a href="{% url "colloscope.marketplace" %}">
<a href="{% url "colloscope:marketplace" %}">
<div class="link"><i class="fa-solid fa-shop"></i> Marketplace</div>
</a>
{% endif %}
</div>
<div class="block">
{% 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">
{% csrf_token %}
<button class="link" type="submit" href="{% url "login" %}">
@ -66,6 +73,6 @@
{% block main %}{% endblock main %}
</main>
<footer>
{% block footer %}&copy; 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 %}&copy; 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>
</body>

50
templates/index.html Normal file
View File

@ -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&nbsp;? Aucun problème&nbsp;: il vous suffit de l'échanger&nbsp;!
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&nbsp;:
</p>
<ol start=0>
<li>Exécutez le code librement&nbsp;;</li>
<li>Modifiez le code librement&nbsp;;</li>
<li>Distribuez le code librement&nbsp;;</li>
<li>Distribuez librement des versions modifiées du code.</li>
</ol>
{% endblock %}