From 0d34d4832adef0ef3bff20c4d894d9c07a18f4e1 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Mon, 24 Jan 2022 22:03:10 -0500 Subject: [PATCH] Add first attempt at locker rental management --- recmaint/settings/base.py | 1 + recmaint/urls.py | 1 + rentals/__init__.py | 0 rentals/admin.py | 14 ++++ rentals/apps.py | 6 ++ rentals/migrations/0001_initial.py | 38 +++++++++++ rentals/migrations/__init__.py | 0 rentals/models.py | 41 ++++++++++++ rentals/templates/rentals/lockers.dj.html | 82 +++++++++++++++++++++++ rentals/tests.py | 3 + rentals/urls.py | 8 +++ rentals/views.py | 10 +++ 12 files changed, 204 insertions(+) create mode 100644 rentals/__init__.py create mode 100644 rentals/admin.py create mode 100644 rentals/apps.py create mode 100644 rentals/migrations/0001_initial.py create mode 100644 rentals/migrations/__init__.py create mode 100644 rentals/models.py create mode 100644 rentals/templates/rentals/lockers.dj.html create mode 100644 rentals/tests.py create mode 100644 rentals/urls.py create mode 100644 rentals/views.py diff --git a/recmaint/settings/base.py b/recmaint/settings/base.py index 455268b..4a38b1f 100644 --- a/recmaint/settings/base.py +++ b/recmaint/settings/base.py @@ -23,6 +23,7 @@ ALLOWED_HOSTS = [] INSTALLED_APPS = [ 'tasks.apps.TasksConfig', + 'rentals.apps.RentalsConfig', 'widget_tweaks', 'markdownx', 'markdownify', diff --git a/recmaint/urls.py b/recmaint/urls.py index 1bb2eec..1178cfa 100644 --- a/recmaint/urls.py +++ b/recmaint/urls.py @@ -23,6 +23,7 @@ from django.contrib.auth.views import LoginView, LogoutView urlpatterns = [ path('', lambda request: redirect('/tasks/')), path('tasks/', include('tasks.urls')), + path('rentals/', include('rentals.urls')), path('admin/', admin.site.urls), path('auth/', include([ path('login/', LoginView.as_view( diff --git a/rentals/__init__.py b/rentals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rentals/admin.py b/rentals/admin.py new file mode 100644 index 0000000..3d09307 --- /dev/null +++ b/rentals/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from .models import LockerBank, LockerRental + +class LockerRentalInline(admin.TabularInline): + model = LockerRental + extra = 0 + +@admin.register(LockerBank) +class LockerBankAdmin(admin.ModelAdmin): + inlines = [LockerRentalInline] + prepopulated_fields = {"slug": ("name",)} + +admin.site.register(LockerRental) diff --git a/rentals/apps.py b/rentals/apps.py new file mode 100644 index 0000000..8f49d49 --- /dev/null +++ b/rentals/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class RentalsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'rentals' diff --git a/rentals/migrations/0001_initial.py b/rentals/migrations/0001_initial.py new file mode 100644 index 0000000..407ee8c --- /dev/null +++ b/rentals/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.11 on 2022-01-24 04:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='LockerBank', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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='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')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/rentals/migrations/__init__.py b/rentals/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rentals/models.py b/rentals/models.py new file mode 100644 index 0000000..86aad63 --- /dev/null +++ b/rentals/models.py @@ -0,0 +1,41 @@ +from django.contrib.auth import get_user_model +from django.db import models + + +class LockerBank(models.Model): + 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})" + + @property + def initial(self): + return self.name[0].upper() + + @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) + + +class LockerRental(models.Model): + locker_bank = models.ForeignKey(LockerBank, 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)? + + @property + def address(self) -> str: + return f"{self.locker_bank.name}-{self.locker_bank.initial}{chr(self.row + ord('A'))}{self.column}" + + 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 new file mode 100644 index 0000000..86f9929 --- /dev/null +++ b/rentals/templates/rentals/lockers.dj.html @@ -0,0 +1,82 @@ +{% extends "base.dj.html" %} + +{% block title %}Lockers Index{% endblock %} +{% block admin_link %}/admin/rentals/{% endblock %} +{% block content %} + + {% for bank in locker_banks %} +
+

{{ bank.name }}

+
{{ bank.location }}
+
+ {% for row, column, rentals in bank.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 %} + {% else %} + Occupied + {% endif %} + {% else %} + Empty + {% endif %} +
+
+ {% endfor %} +
+
+ {% endfor %} +{% endblock %} diff --git a/rentals/tests.py b/rentals/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/rentals/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/rentals/urls.py b/rentals/urls.py new file mode 100644 index 0000000..0670b22 --- /dev/null +++ b/rentals/urls.py @@ -0,0 +1,8 @@ +from django.shortcuts import redirect +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.lockerIndex, name="index"), +] diff --git a/rentals/views.py b/rentals/views.py new file mode 100644 index 0000000..4b0518f --- /dev/null +++ b/rentals/views.py @@ -0,0 +1,10 @@ +from django.shortcuts import render + +from .models import LockerBank, LockerRental + + +def lockerIndex(request): + context = { + "locker_banks": LockerBank.objects.all(), + } + return render(request, "rentals/lockers.dj.html", context)