mirror of
https://github.com/ae-utbm/sith.git
synced 2025-06-07 11:45:20 +00:00
create reservation models
This commit is contained in:
parent
b1eae5b466
commit
fd28fb1493
@ -788,7 +788,16 @@ class Command(BaseCommand):
|
||||
|
||||
subscribers = Group.objects.create(name="Subscribers")
|
||||
subscribers.permissions.add(
|
||||
*list(perms.filter(codename__in=["add_news", "add_uvcomment"]))
|
||||
*list(
|
||||
perms.filter(
|
||||
codename__in=[
|
||||
"add_news",
|
||||
"add_uvcomment",
|
||||
"add_reservationslot",
|
||||
"view_reservationslot",
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
old_subscribers = Group.objects.create(name="Old subscribers")
|
||||
old_subscribers.permissions.add(
|
||||
|
0
reservation/__init__.py
Normal file
0
reservation/__init__.py
Normal file
19
reservation/admin.py
Normal file
19
reservation/admin.py
Normal file
@ -0,0 +1,19 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from reservation.models import ReservationSlot, Room
|
||||
|
||||
|
||||
@admin.register(Room)
|
||||
class RoomAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "club")
|
||||
list_filter = (("club", admin.RelatedOnlyFieldListFilter), "location")
|
||||
autocomplete_fields = ("club",)
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
@admin.register(ReservationSlot)
|
||||
class ReservationSlotAdmin(admin.ModelAdmin):
|
||||
list_display = ("room", "start_at", "duration", "author")
|
||||
autocomplete_fields = ("author",)
|
||||
list_filter = ("room",)
|
||||
date_hierarchy = "start_at"
|
6
reservation/apps.py
Normal file
6
reservation/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ReservationConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "reservation"
|
144
reservation/migrations/0001_initial.py
Normal file
144
reservation/migrations/0001_initial.py
Normal file
@ -0,0 +1,144 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-05 10:44
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("club", "0014_alter_club_options_rename_unix_name_club_slug_name_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Room",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100, verbose_name="room name")),
|
||||
(
|
||||
"description",
|
||||
models.TextField(
|
||||
blank=True, default="", verbose_name="description"
|
||||
),
|
||||
),
|
||||
(
|
||||
"logo",
|
||||
core.fields.ResizedImageField(
|
||||
blank=True,
|
||||
force_format="WEBP",
|
||||
height=100,
|
||||
upload_to="rooms",
|
||||
verbose_name="logo",
|
||||
width=100,
|
||||
),
|
||||
),
|
||||
(
|
||||
"location",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("BELFORT", "Belfort"),
|
||||
("SEVENANS", "Sévenans"),
|
||||
("MONTBELIARD", "Montbéliard"),
|
||||
],
|
||||
verbose_name="site",
|
||||
),
|
||||
),
|
||||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
help_text="The club which manages this room",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="reservable_rooms",
|
||||
to="club.club",
|
||||
verbose_name="room owner",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "reservable room",
|
||||
"verbose_name_plural": "reservable rooms",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ReservationSlot",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"nb_people",
|
||||
models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
help_text="How many people will attend this reservation slot",
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="number of people",
|
||||
),
|
||||
),
|
||||
(
|
||||
"comment",
|
||||
models.TextField(blank=True, default="", verbose_name="comment"),
|
||||
),
|
||||
(
|
||||
"start_at",
|
||||
models.DateTimeField(db_index=True, verbose_name="slot start"),
|
||||
),
|
||||
("duration", models.DurationField(verbose_name="duration")),
|
||||
(
|
||||
"end_at",
|
||||
models.GeneratedField(
|
||||
db_persist=False,
|
||||
expression=django.db.models.expressions.CombinedExpression(
|
||||
models.F("start_at"), "+", models.F("duration")
|
||||
),
|
||||
output_field=models.DateTimeField(),
|
||||
verbose_name="slot end",
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="author",
|
||||
),
|
||||
),
|
||||
(
|
||||
"room",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="slots",
|
||||
to="reservation.room",
|
||||
verbose_name="reserved room",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "reservation slot",
|
||||
"verbose_name_plural": "reservation slots",
|
||||
},
|
||||
),
|
||||
]
|
0
reservation/migrations/__init__.py
Normal file
0
reservation/migrations/__init__.py
Normal file
101
reservation/models.py
Normal file
101
reservation/models.py
Normal file
@ -0,0 +1,101 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Self
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import F, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from club.models import Club
|
||||
from core.fields import ResizedImageField
|
||||
from core.models import User
|
||||
|
||||
|
||||
class Room(models.Model):
|
||||
name = models.CharField(_("room name"), max_length=100)
|
||||
description = models.TextField(_("description"), blank=True, default="")
|
||||
logo = ResizedImageField(
|
||||
width=100,
|
||||
height=100,
|
||||
force_format="WEBP",
|
||||
upload_to="rooms",
|
||||
verbose_name=_("logo"),
|
||||
blank=True,
|
||||
)
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="reservable_rooms",
|
||||
verbose_name=_("room owner"),
|
||||
help_text=_("The club which manages this room"),
|
||||
)
|
||||
location = models.CharField(
|
||||
_("site"),
|
||||
blank=True,
|
||||
choices=[
|
||||
("BELFORT", "Belfort"),
|
||||
("SEVENANS", "Sévenans"),
|
||||
("MONTBELIARD", "Montbéliard"),
|
||||
],
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("reservable room")
|
||||
verbose_name_plural = _("reservable rooms")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ReservationSlotQuerySet(models.QuerySet):
|
||||
def overlapping_with(self, other: ReservationSlot) -> Self:
|
||||
return self.filter(
|
||||
Q(end_at__gt=other.start_at, end_ad__lt=other.end_at)
|
||||
| Q(start_at__lt=other.start_at, start_ad__gt=other.start_at)
|
||||
)
|
||||
|
||||
|
||||
class ReservationSlot(models.Model):
|
||||
room = models.ForeignKey(
|
||||
Room,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="slots",
|
||||
verbose_name=_("reserved room"),
|
||||
)
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("author"))
|
||||
nb_people = models.PositiveSmallIntegerField(
|
||||
verbose_name=_("number of people"),
|
||||
help_text=_("How many people will attend this reservation slot"),
|
||||
default=1,
|
||||
validators=[MinValueValidator(1)],
|
||||
)
|
||||
comment = models.TextField(_("comment"), blank=True, default="")
|
||||
start_at = models.DateTimeField(_("slot start"), db_index=True)
|
||||
duration = models.DurationField(_("duration"))
|
||||
end_at = models.GeneratedField(
|
||||
verbose_name=_("slot end"),
|
||||
expression=F("start_at") + F("duration"),
|
||||
output_field=models.DateTimeField(),
|
||||
db_persist=False,
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = ReservationSlotQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("reservation slot")
|
||||
verbose_name_plural = _("reservation slots")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.room.name} : {self.start_at} - {self.end_at}"
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if (
|
||||
ReservationSlot.objects.overlapping_with(self)
|
||||
.filter(room_id=self.room_id)
|
||||
.exists()
|
||||
):
|
||||
raise ValidationError(_("There is already a reservation on this slot."))
|
1
reservation/tests.py
Normal file
1
reservation/tests.py
Normal file
@ -0,0 +1 @@
|
||||
# Create your tests here.
|
1
reservation/views.py
Normal file
1
reservation/views.py
Normal file
@ -0,0 +1 @@
|
||||
# Create your views here.
|
@ -123,6 +123,7 @@ INSTALLED_APPS = (
|
||||
"trombi",
|
||||
"matmat",
|
||||
"pedagogy",
|
||||
"reservation",
|
||||
"galaxy",
|
||||
"antispam",
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user