commit
23c79e2284
14 changed files with 316 additions and 8 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
.idea/
|
||||
Palto/db.sqlite3
|
||||
.idea
|
||||
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 80
|
||||
|
||||
# Set the working directory in the Django application
|
||||
WORKDIR ./Palto
|
||||
|
||||
# Start the Django application
|
||||
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',
|
||||
"debug_toolbar",
|
||||
'django_extensions',
|
||||
|
||||
"Palto.Palto",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -129,7 +131,13 @@ USE_TZ = True
|
|||
# Static files (CSS, JavaScript, Images)
|
||||
# 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
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
@ -145,3 +153,6 @@ REST_FRAMEWORK = {
|
|||
'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'))
|
||||
"""
|
||||
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 = [
|
||||
# Application
|
||||
# ...
|
||||
|
||||
# API
|
||||
path('rest/', include('rest_framework.urls')), # API REST
|
||||
path('api/', include('rest_framework.urls')), # API REST
|
||||
|
||||
# Debug
|
||||
path('admin/', admin.site.urls), # Admin page
|
||||
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