diff --git a/rentals/admin.py b/rentals/admin.py index 0bd4bf5..db93b91 100644 --- a/rentals/admin.py +++ b/rentals/admin.py @@ -1,6 +1,17 @@ from django.contrib import admin -from .models import LockerBank, LockerRental +from .models import LockerBank, LockerRental, LockerUnit + + +class LockerUnitInline(admin.TabularInline): + model = LockerUnit + extra = 0 + + +@admin.register(LockerBank) +class LockerBankAdmin(admin.ModelAdmin): + inlines = [LockerUnitInline] + prepopulated_fields = {"slug": ("name",)} class LockerRentalInline(admin.TabularInline): @@ -8,10 +19,9 @@ class LockerRentalInline(admin.TabularInline): extra = 0 -@admin.register(LockerBank) -class LockerBankAdmin(admin.ModelAdmin): +@admin.register(LockerUnit) +class LockerUnitAdmin(admin.ModelAdmin): inlines = [LockerRentalInline] - prepopulated_fields = {"slug": ("name",)} admin.site.register(LockerRental) diff --git a/rentals/migrations/0001_initial.py b/rentals/migrations/0001_initial.py index 407ee8c..3f78ee2 100644 --- a/rentals/migrations/0001_initial.py +++ b/rentals/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 3.2.11 on 2022-01-24 04:08 +# Generated by Django 3.2.11 on 2022-01-27 22:14 from django.conf import settings +import django.core.validators from django.db import migrations, models import django.db.models.deletion @@ -21,18 +22,35 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=200)), ('location', models.CharField(max_length=200)), ('slug', models.SlugField(unique=True)), - ('rows', models.PositiveIntegerField()), - ('columns', models.PositiveIntegerField()), ], ), + migrations.CreateModel( + name='LockerUnit', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('index', models.PositiveIntegerField()), + ('first_letter', models.CharField(max_length=1, unique=True, validators=[django.core.validators.RegexValidator('[A-Z]')])), + ('first_number', models.PositiveIntegerField()), + ('rows', models.PositiveIntegerField(default=5)), + ('columns', models.PositiveIntegerField(default=2)), + ('bank', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='units', to='rentals.lockerbank')), + ], + options={ + 'ordering': ['index'], + }, + ), migrations.CreateModel( name='LockerRental', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('row', models.PositiveIntegerField()), ('column', models.PositiveIntegerField()), - ('locker_bank', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rentals.lockerbank')), + ('row', models.PositiveIntegerField()), + ('locker_unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rentals', to='rentals.lockerunit')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), + migrations.AddConstraint( + model_name='lockerunit', + constraint=models.UniqueConstraint(fields=('bank', 'index'), name='unique_bank_index'), + ), ] diff --git a/rentals/models.py b/rentals/models.py index 9563886..58938cb 100644 --- a/rentals/models.py +++ b/rentals/models.py @@ -1,45 +1,75 @@ from django.contrib.auth import get_user_model +from django.core import validators from django.db import models class LockerBank(models.Model): + """A set of locker units, placed together""" + name = models.CharField(max_length=200) location = models.CharField(max_length=200) slug = models.SlugField(unique=True) - rows = models.PositiveIntegerField() - columns = models.PositiveIntegerField() - # TODO: add constraint for unique first letter? def __str__(self): - return f"{self.name} ({self.columns}x{self.rows})" + return f"{self.name} ({self.units.count()} units)" - @property - def initial(self): - return self.name[0].upper() + +class LockerUnit(models.Model): + """A standalone set of lockers""" + + bank = models.ForeignKey( + LockerBank, on_delete=models.SET_NULL, related_name="units", null=True + ) + index = models.PositiveIntegerField() + first_letter = models.CharField( + max_length=1, validators=[validators.RegexValidator("[A-Z]")], unique=True + ) + first_number = models.PositiveIntegerField() + rows = models.PositiveIntegerField(default=5) + columns = models.PositiveIntegerField(default=2) + + class Meta: + # TODO: add constraint to check for letter overlaps + constraints = [ + models.UniqueConstraint(fields=["bank", "index"], name="unique_bank_index") + ] + ordering = ["index"] + + def letter_for_column(self, column: int) -> str: + return chr(column + ord(self.first_letter)) + + def number_for_locker(self, column: int, row: int) -> int: + return row + self.first_number + column * self.rows @property def iter_doors(self): for row in range(self.rows): for column in range(self.columns): # TODO: filter could be optimized - yield chr(row + ord("A")), column + 1, self.rentals.filter( - row=row, column=column + yield ( + self.letter_for_column(column), + self.number_for_locker(column, row), + self.rentals.filter(row=row, column=column), ) +# 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): - locker_bank = models.ForeignKey( - LockerBank, on_delete=models.CASCADE, related_name="rentals" + """A rental of a single locker""" + + locker_unit = models.ForeignKey( + LockerUnit, on_delete=models.CASCADE, related_name="rentals" ) - user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) - row = models.PositiveIntegerField() column = models.PositiveIntegerField() - # TODO: add check constraint to ensure that row and column are within locker_bank bounds - # TODO: add unique constraint on (bank, row, column)? + row = models.PositiveIntegerField() + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) @property def address(self) -> str: - return f"{self.locker_bank.name}-{self.locker_bank.initial}{chr(self.row + ord('A'))}{self.column}" + letter = self.locker_unit.letter_for_column(self.column) + number = self.locker_unit.number_for_locker(self.column, self.row) + return f"{self.locker_unit}-{letter}{number}" def __str__(self): return f"{self.user}: {self.address}" diff --git a/rentals/templates/rentals/lockers.dj.html b/rentals/templates/rentals/lockers.dj.html index 89b6248..23d5a39 100644 --- a/rentals/templates/rentals/lockers.dj.html +++ b/rentals/templates/rentals/lockers.dj.html @@ -1,7 +1,9 @@ {% extends "base.dj.html" %} {% block title %}Lockers Index{% endblock %} -{% block admin_link %}{% url 'admin:app_list' 'rentals' %}{% endblock %} +{% block admin_link %} + {% url 'admin:app_list' 'rentals' %} +{% endblock %} {% block content %} {% for bank in locker_banks %} -