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.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),)
|
||||
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",
|
||||
"matmat",
|
||||
"pedagogy",
|
||||
"reservation",
|
||||
"galaxy",
|
||||
"antispam",
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user