diff --git a/rentals/admin.py b/rentals/admin.py index b969b78..4c88c55 100644 --- a/rentals/admin.py +++ b/rentals/admin.py @@ -1,6 +1,11 @@ from django.contrib import admin -from .models import LockerBank, LockerRental, LockerUnit +from .models import LockerBank, LockerInfo, LockerUnit + + +class LockerInfoInline(admin.TabularInline): + model = LockerInfo + extra = 0 class LockerUnitInline(admin.TabularInline): @@ -14,16 +19,14 @@ class LockerBankAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} -class LockerRentalInline(admin.TabularInline): - model = LockerRental - extra = 0 - - @admin.register(LockerUnit) class LockerUnitAdmin(admin.ModelAdmin): - inlines = [LockerRentalInline] + inlines = [LockerInfoInline] -@admin.register(LockerRental) -class LockerRentalAdmin(admin.ModelAdmin): - search_fields = ["user__username", "locker_unit__bank__name"] +@admin.register(LockerInfo) +class LockerInfoAdmin(admin.ModelAdmin): + search_fields = ["renter__username", "locker_unit__bank__name"] + list_filter = ["locker_unit__bank", "locker_unit", "renter"] + list_display = ["locker_unit", "address", "blind_code", "bitting_code", "renter"] + list_display_links = ["locker_unit", "address"] diff --git a/rentals/migrations/0001_initial.py b/rentals/migrations/0001_initial.py index dc6b5c7..7e857b7 100644 --- a/rentals/migrations/0001_initial.py +++ b/rentals/migrations/0001_initial.py @@ -1,6 +1,5 @@ -# Generated by Django 3.2.11 on 2022-01-27 22:14 +# Generated by Django 4.0.2 on 2022-02-16 21:19 -from django.conf import settings import django.core.validators from django.db import migrations, models import django.db.models.deletion @@ -11,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("membershipworks", "0001_initial"), ] operations = [ @@ -71,7 +70,7 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name="LockerRental", + name="LockerInfo", fields=[ ( "id", @@ -84,19 +83,38 @@ class Migration(migrations.Migration): ), ("column", models.PositiveIntegerField()), ("row", models.PositiveIntegerField()), + ( + "blind_code", + models.CharField( + blank=True, + help_text="Stamped on some keys. Usually D###A.", + max_length=5, + ), + ), + ( + "bitting_code", + models.CharField( + blank=True, + help_text="National Disc Tumbler, depths 1-4. Read bow-to-tip.", + max_length=5, + ), + ), ( "locker_unit", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - related_name="rentals", + related_name="lockers", to="rentals.lockerunit", ), ), ( - "user", + "renter", models.ForeignKey( + blank=True, + db_constraint=False, + null=True, on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, + to="membershipworks.member", ), ), ], @@ -107,4 +125,10 @@ class Migration(migrations.Migration): fields=("bank", "index"), name="unique_bank_index" ), ), + migrations.AddConstraint( + model_name="lockerinfo", + constraint=models.UniqueConstraint( + fields=("locker_unit", "column", "row"), name="unique_locker_info" + ), + ), ] diff --git a/rentals/models.py b/rentals/models.py index 252b8f9..84b9119 100644 --- a/rentals/models.py +++ b/rentals/models.py @@ -1,7 +1,8 @@ -from django.contrib.auth import get_user_model from django.core import validators from django.db import models +from membershipworks.models import Member + class LockerBank(models.Model): """A set of locker units, placed together""" @@ -46,29 +47,50 @@ class LockerUnit(models.Model): def number_for_locker(self, column: int, row: int) -> int: return row + self.first_number + (self.columns - column - 1) * self.rows - @property def iter_doors(self): for row in range(self.rows): for column in range(self.columns): # TODO: filter could be optimized + (locker, _) = self.lockers.get_or_create( + locker_unit=self, row=row, column=column + ) + yield ( self.letter_for_column(column), self.number_for_locker(column, row), - self.rentals.filter(row=row, column=column), + locker, ) # TODO: add check constraint to ensure that column and number are within locker_unit bounds -# TODO: add unique constraint on (unit, row, column)? -class LockerRental(models.Model): - """A rental of a single locker""" +class LockerInfo(models.Model): + """Information about a single locker""" locker_unit = models.ForeignKey( - LockerUnit, on_delete=models.CASCADE, related_name="rentals" + LockerUnit, on_delete=models.CASCADE, related_name="lockers" ) column = models.PositiveIntegerField() row = models.PositiveIntegerField() - user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) + blind_code = models.CharField( + max_length=5, + help_text="Stamped on some keys. Usually D###A.", + blank=True, + ) + bitting_code = models.CharField( + max_length=5, + help_text="National Disc Tumbler, depths 1-4. Read bow-to-tip.", + blank=True, + ) + renter = models.ForeignKey( + Member, on_delete=models.CASCADE, null=True, blank=True, db_constraint=False + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["locker_unit", "column", "row"], name="unique_locker_info" + ) + ] @property def address(self) -> str: @@ -77,4 +99,4 @@ class LockerRental(models.Model): return f"{letter}{number}" def __str__(self): - return f"{self.user}: {self.locker_unit}-{self.address}" + return f"{self.locker_unit}-{self.address} [{self.renter}]" diff --git a/rentals/templates/rentals/lockers.dj.html b/rentals/templates/rentals/lockers.dj.html index 23d5a39..9ef3b30 100644 --- a/rentals/templates/rentals/lockers.dj.html +++ b/rentals/templates/rentals/lockers.dj.html @@ -7,20 +7,21 @@ {% block content %} - {% for bank in locker_banks %} + {% for bank, units in locker_banks.items %}

{{ bank.name }}

{{ bank.location }}
- {% for unit in bank.units.all %} + {% for unit, lockers in units.items %}
- {% for row, column, rentals in unit.iter_doors %} -
-

{{ bank.initial }}{{ row }}{{ column }}

-
- {% if rentals|length > 0 %} - {# TODO: Should check for more specific permission #} - {% if user.is_staff %} - {% for rental in rentals %}{{ rental.user }}{% endfor %} + {% for row, column, locker, form in lockers %} + + + {% if perms.rentals.view_lockerinfo %} + + {% endif %}
{% endfor %}
diff --git a/rentals/urls.py b/rentals/urls.py index 9ea46a0..711dcdd 100644 --- a/rentals/urls.py +++ b/rentals/urls.py @@ -6,4 +6,5 @@ app_name = "rentals" urlpatterns = [ path("", views.lockerIndex, name="index"), + path("locker/", views.lockerUpdate, name="locker"), ] diff --git a/rentals/views.py b/rentals/views.py index 4b0518f..d25da1e 100644 --- a/rentals/views.py +++ b/rentals/views.py @@ -1,10 +1,44 @@ -from django.shortcuts import render +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.shortcuts import render, reverse -from .models import LockerBank, LockerRental +from .models import LockerBank, LockerInfo + +from .forms import LockerInfoForm def lockerIndex(request): + locker_banks = { + bank: { + unit: [ + (col, num, locker, LockerInfoForm(instance=locker)) + for col, num, locker in unit.iter_doors() + ] + for unit in bank.units.all() + } + for bank in LockerBank.objects.all() + } + context = { - "locker_banks": LockerBank.objects.all(), + "locker_banks": locker_banks, } return render(request, "rentals/lockers.dj.html", context) + + +@login_required +@permission_required("rentals.change_lockerinfo", raise_exception=True) +def lockerUpdate(request, locker_id: int): + if request.method == "POST": + try: + instance = LockerInfo.objects.get(pk=locker_id) + except LockerInfo.DoesNotExist: + pass # TODO + + form = LockerInfoForm(request.POST, instance=instance) + if form.is_valid(): + form.save() + else: + messages.add_message(request, messages.ERROR, form.errors) + + return HttpResponseRedirect(reverse("rentals:index"))