commit
23c79e2284
14 changed files with 316 additions and 8 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
||||||
.idea/
|
.idea
|
||||||
Palto/db.sqlite3
|
venv
|
||||||
|
media
|
||||||
|
static-collected
|
||||||
|
db.sqlite3
|
||||||
|
|
|
@ -16,8 +16,5 @@ RUN pip install -r requirements.txt
|
||||||
# Expose the port on which your Django application will run
|
# Expose the port on which your Django application will run
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
# Set the working directory in the Django application
|
|
||||||
WORKDIR ./Palto
|
|
||||||
|
|
||||||
# Start the Django application
|
# Start the Django application
|
||||||
ENTRYPOINT ["python", "manage.py", "runserver_plus", "0.0.0.0:80"]
|
ENTRYPOINT ["python", "manage.py", "runserver_plus", "0.0.0.0:80"]
|
||||||
|
|
63
Palto/Palto/admin.py
Normal file
63
Palto/Palto/admin.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import (Department, StudentGroup, TeachingUnit, StudentCard, TeachingSession, Attendance, Absence,
|
||||||
|
AbsenceAttachment, User)
|
||||||
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
@admin.register(User)
|
||||||
|
class AdminUser(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "username", "email", "first_name", "last_name", "is_staff")
|
||||||
|
search_fields = ("id", "username", "email", "first_name", "last_name", "is_staff")
|
||||||
|
list_filter = ("is_staff",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Department)
|
||||||
|
class AdminDepartment(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "name")
|
||||||
|
search_fields = ("id", "name")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(StudentGroup)
|
||||||
|
class AdminStudentGroup(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "name")
|
||||||
|
search_fields = ("id", "name")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TeachingUnit)
|
||||||
|
class AdminTeachingUnit(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "name")
|
||||||
|
search_fields = ("id", "name")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(StudentCard)
|
||||||
|
class AdminStudentCard(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "uid", "owner")
|
||||||
|
search_fields = ("id", "uid", "owner")
|
||||||
|
readonly_fields = ("uid",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TeachingSession)
|
||||||
|
class AdminTeachingSession(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "start", "end", "duration", "teacher")
|
||||||
|
search_fields = ("id", "start", "end", "duration", "teacher")
|
||||||
|
list_filter = ("start", "duration")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Attendance)
|
||||||
|
class AdminAttendance(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "date", "student")
|
||||||
|
search_fields = ("id", "date", "student")
|
||||||
|
list_filter = ("date",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Absence)
|
||||||
|
class AdminAbsence(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "message", "student")
|
||||||
|
search_fields = ("id", "message", "student")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AbsenceAttachment)
|
||||||
|
class AdminAbsenceAttachment(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "content", "absence")
|
||||||
|
search_fields = ("id", "content", "absence")
|
7
Palto/Palto/apps.py
Normal file
7
Palto/Palto/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PaltoConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = "Palto.Palto"
|
||||||
|
label = "Palto"
|
0
Palto/Palto/migrations/__init__.py
Normal file
0
Palto/Palto/migrations/__init__.py
Normal file
210
Palto/Palto/models.py
Normal file
210
Palto/Palto/models.py
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
class User(AbstractUser):
|
||||||
|
"""
|
||||||
|
A user.
|
||||||
|
|
||||||
|
Same as the base Django user, but the id is now an uuid instead of an int.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Department(models.Model):
|
||||||
|
"""
|
||||||
|
A scholar department.
|
||||||
|
|
||||||
|
For example, a same server can handle both a science department and a sport department.
|
||||||
|
ALl have their own managers, teachers and student.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
name: str = models.CharField(max_length=64)
|
||||||
|
mail: str = models.EmailField()
|
||||||
|
|
||||||
|
managers = models.ManyToManyField(to=get_user_model(), blank=True, related_name="managing_departments")
|
||||||
|
teachers = models.ManyToManyField(to=get_user_model(), blank=True, related_name="teaching_departments")
|
||||||
|
students = models.ManyToManyField(to=get_user_model(), blank=True, related_name="studying_departments")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class StudentGroup(models.Model):
|
||||||
|
"""
|
||||||
|
A student group.
|
||||||
|
|
||||||
|
This make selecting multiple students with a specificity easier.
|
||||||
|
|
||||||
|
For example, if students are registered to an English course,
|
||||||
|
putting them in a same group make them easier to select.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
name: str = models.CharField(max_length=128)
|
||||||
|
|
||||||
|
students = models.ManyToManyField(to=get_user_model(), blank=True, related_name="student_groups")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class TeachingUnit(models.Model):
|
||||||
|
"""
|
||||||
|
A teaching unit.
|
||||||
|
|
||||||
|
This represents a unit that can be taught to groups of student.
|
||||||
|
|
||||||
|
For example, Maths, English, French, Computer Science are all teaching units.
|
||||||
|
The registered groups are groups of student allowed to participate in these units.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
name: str = models.CharField(max_length=64)
|
||||||
|
|
||||||
|
department = models.ForeignKey(to=Department, on_delete=models.CASCADE, related_name="teaching_units")
|
||||||
|
|
||||||
|
managers = models.ManyToManyField(to=get_user_model(), blank=True, related_name="managing_units")
|
||||||
|
teachers = models.ManyToManyField(to=get_user_model(), blank=True, related_name="teaching_units")
|
||||||
|
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}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class StudentCard(models.Model):
|
||||||
|
"""
|
||||||
|
A student card.
|
||||||
|
|
||||||
|
This represents a student NFC card.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
uid: bytes = models.BinaryField(max_length=7)
|
||||||
|
|
||||||
|
owner: User = models.ForeignKey(to=get_user_model(), 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}>"
|
||||||
|
|
||||||
|
|
||||||
|
class TeachingSession(models.Model):
|
||||||
|
"""
|
||||||
|
A session of a teaching unit.
|
||||||
|
|
||||||
|
For example, a session of English would be a single course of this unit.
|
||||||
|
|
||||||
|
It references a teacher responsible for scanning the student cards, student attendances and student absences.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
start: datetime = models.DateTimeField()
|
||||||
|
duration: timedelta = models.DurationField()
|
||||||
|
note: str = models.TextField(blank=True)
|
||||||
|
|
||||||
|
unit = models.ForeignKey(to=TeachingUnit, on_delete=models.CASCADE, related_name="sessions")
|
||||||
|
|
||||||
|
group = models.ForeignKey(to=StudentGroup, on_delete=models.CASCADE, related_name="teaching_sessions")
|
||||||
|
teacher = models.ForeignKey(to=get_user_model(), 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}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.unit.name} ({self.start})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end(self) -> datetime:
|
||||||
|
return self.start + self.duration
|
||||||
|
|
||||||
|
|
||||||
|
class Attendance(models.Model):
|
||||||
|
"""
|
||||||
|
A student attendance to a session.
|
||||||
|
|
||||||
|
When a student confirm his presence to a session, this is represented by this model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
date: datetime = models.DateTimeField()
|
||||||
|
|
||||||
|
student: User = models.ForeignKey(
|
||||||
|
to=get_user_model(),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="attended_sessions"
|
||||||
|
)
|
||||||
|
session: TeachingSession = models.ForeignKey(
|
||||||
|
to=TeachingSession,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="attendances"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
f"<{self.__class__.__name__} "
|
||||||
|
f"id={str(self.id)[:8]} "
|
||||||
|
f"student={self.student.username} "
|
||||||
|
f"session={str(self.session.id)[:8]}"
|
||||||
|
f">"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Absence(models.Model):
|
||||||
|
"""
|
||||||
|
A student justified absence to a session.
|
||||||
|
|
||||||
|
When a student signal his absence to a session, this is represented by this model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
message: str = models.TextField()
|
||||||
|
|
||||||
|
student: User = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE, related_name="absented_sessions")
|
||||||
|
session: TeachingSession = models.ManyToManyField(to=TeachingSession, blank=True, related_name="absences")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
f"<{self.__class__.__name__} "
|
||||||
|
f"id={str(self.id)[:8]} "
|
||||||
|
f"student={self.student.username} "
|
||||||
|
f"session={str(self.session.id)[:8]}"
|
||||||
|
f">"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"[{str(self.id)[:8]}] {self.student}"
|
||||||
|
|
||||||
|
|
||||||
|
class AbsenceAttachment(models.Model):
|
||||||
|
"""
|
||||||
|
An attachment to a student justified absence.
|
||||||
|
|
||||||
|
The student can add additional files to justify his absence.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
|
content = models.FileField(upload_to="absence/attachment/")
|
||||||
|
|
||||||
|
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}>"
|
3
Palto/Palto/tests.py
Normal file
3
Palto/Palto/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
Palto/Palto/views.py
Normal file
3
Palto/Palto/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
0
Palto/__init__.py
Normal file
0
Palto/__init__.py
Normal file
|
@ -50,6 +50,8 @@ INSTALLED_APPS = [
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
|
|
||||||
|
"Palto.Palto",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -129,7 +131,13 @@ USE_TZ = True
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = "static/"
|
||||||
|
STATIC_ROOT = BASE_DIR / "static-collected/"
|
||||||
|
|
||||||
|
# Media files
|
||||||
|
|
||||||
|
MEDIA_URL = "media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media/"
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
@ -145,3 +153,6 @@ REST_FRAMEWORK = {
|
||||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# User model
|
||||||
|
AUTH_USER_MODEL = "Palto.User"
|
|
@ -15,16 +15,27 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include, re_path
|
||||||
|
from django.views.static import serve
|
||||||
|
|
||||||
|
from Palto import settings
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Application
|
# Application
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
# API
|
# API
|
||||||
path('rest/', include('rest_framework.urls')), # API REST
|
path('api/', include('rest_framework.urls')), # API REST
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
path('admin/', admin.site.urls), # Admin page
|
path('admin/', admin.site.urls), # Admin page
|
||||||
path("__debug__/", include("debug_toolbar.urls")), # Debug toolbar
|
path("__debug__/", include("debug_toolbar.urls")), # Debug toolbar
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += [
|
||||||
|
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||||
|
re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}),
|
||||||
|
]
|
0
Palto/manage.py → manage.py
Executable file → Normal file
0
Palto/manage.py → manage.py
Executable file → Normal file
Loading…
Reference in a new issue