mirror of
https://github.com/ae-utbm/sith.git
synced 2025-05-05 00:54:06 +00:00
create reservation models
This commit is contained in:
parent
4f5a69c353
commit
691d956c0e
@ -812,7 +812,16 @@ Welcome to the wiki page!
|
|||||||
|
|
||||||
subscribers = Group.objects.create(name="Subscribers")
|
subscribers = Group.objects.create(name="Subscribers")
|
||||||
subscribers.permissions.add(
|
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 = Group.objects.create(name="Old subscribers")
|
||||||
old_subscribers.permissions.add(
|
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),)
|
||||||
|
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"
|
141
reservation/migrations/0001_initial.py
Normal file
141
reservation/migrations/0001_initial.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# Generated by Django 5.2 on 2025-04-21 14:19
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("address", models.CharField(max_length=255, verbose_name="address")),
|
||||||
|
(
|
||||||
|
"club",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="reservable_rooms",
|
||||||
|
to="club.club",
|
||||||
|
verbose_name="room owner",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"occupancy",
|
||||||
|
models.PositiveSmallIntegerField(
|
||||||
|
default=1,
|
||||||
|
help_text="The maximum number of people this room can host at once",
|
||||||
|
validators=[django.core.validators.MinValueValidator(1)],
|
||||||
|
verbose_name="maximum occupancy",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
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
107
reservation/models.py
Normal file
107
reservation/models.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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
|
||||||
|
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"),
|
||||||
|
)
|
||||||
|
address = models.CharField(_("address"), max_length=255)
|
||||||
|
occupancy = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=_("maximum occupancy"),
|
||||||
|
help_text=_("The maximum number of people this room can host at once"),
|
||||||
|
validators=[MinValueValidator(1)],
|
||||||
|
)
|
||||||
|
|
||||||
|
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 self.nb_people > self.room.occupancy:
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"You declared an attendance of %(nb_people)d, "
|
||||||
|
"but this room can only host %(occupancy)d people."
|
||||||
|
)
|
||||||
|
% {"nb_people": self.nb_people, "occupancy": self.room.occupancy}
|
||||||
|
)
|
||||||
|
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.
|
@ -121,6 +121,7 @@ INSTALLED_APPS = (
|
|||||||
"trombi",
|
"trombi",
|
||||||
"matmat",
|
"matmat",
|
||||||
"pedagogy",
|
"pedagogy",
|
||||||
|
"reservation",
|
||||||
"galaxy",
|
"galaxy",
|
||||||
"antispam",
|
"antispam",
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user