rentals: Change numbering scheme to match reality
This commit is contained in:
parent
edc66865e0
commit
91255cb06c
@ -1,6 +1,17 @@
|
|||||||
from django.contrib import admin
|
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):
|
class LockerRentalInline(admin.TabularInline):
|
||||||
@ -8,10 +19,9 @@ class LockerRentalInline(admin.TabularInline):
|
|||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(LockerBank)
|
@admin.register(LockerUnit)
|
||||||
class LockerBankAdmin(admin.ModelAdmin):
|
class LockerUnitAdmin(admin.ModelAdmin):
|
||||||
inlines = [LockerRentalInline]
|
inlines = [LockerRentalInline]
|
||||||
prepopulated_fields = {"slug": ("name",)}
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(LockerRental)
|
admin.site.register(LockerRental)
|
||||||
|
@ -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
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
@ -21,18 +22,35 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(max_length=200)),
|
('name', models.CharField(max_length=200)),
|
||||||
('location', models.CharField(max_length=200)),
|
('location', models.CharField(max_length=200)),
|
||||||
('slug', models.SlugField(unique=True)),
|
('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(
|
migrations.CreateModel(
|
||||||
name='LockerRental',
|
name='LockerRental',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('row', models.PositiveIntegerField()),
|
|
||||||
('column', 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)),
|
('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'),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,45 +1,75 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core import validators
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class LockerBank(models.Model):
|
class LockerBank(models.Model):
|
||||||
|
"""A set of locker units, placed together"""
|
||||||
|
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
location = models.CharField(max_length=200)
|
location = models.CharField(max_length=200)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
rows = models.PositiveIntegerField()
|
|
||||||
columns = models.PositiveIntegerField()
|
|
||||||
# TODO: add constraint for unique first letter?
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} ({self.columns}x{self.rows})"
|
return f"{self.name} ({self.units.count()} units)"
|
||||||
|
|
||||||
@property
|
|
||||||
def initial(self):
|
class LockerUnit(models.Model):
|
||||||
return self.name[0].upper()
|
"""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
|
@property
|
||||||
def iter_doors(self):
|
def iter_doors(self):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for column in range(self.columns):
|
for column in range(self.columns):
|
||||||
# TODO: filter could be optimized
|
# TODO: filter could be optimized
|
||||||
yield chr(row + ord("A")), column + 1, self.rentals.filter(
|
yield (
|
||||||
row=row, column=column
|
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):
|
class LockerRental(models.Model):
|
||||||
locker_bank = models.ForeignKey(
|
"""A rental of a single locker"""
|
||||||
LockerBank, on_delete=models.CASCADE, related_name="rentals"
|
|
||||||
|
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()
|
column = models.PositiveIntegerField()
|
||||||
# TODO: add check constraint to ensure that row and column are within locker_bank bounds
|
row = models.PositiveIntegerField()
|
||||||
# TODO: add unique constraint on (bank, row, column)?
|
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def address(self) -> str:
|
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):
|
def __str__(self):
|
||||||
return f"{self.user}: {self.address}"
|
return f"{self.user}: {self.address}"
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{% extends "base.dj.html" %}
|
{% extends "base.dj.html" %}
|
||||||
|
|
||||||
{% block title %}Lockers Index{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
.locker-bank {
|
.locker-bank {
|
||||||
@ -9,12 +11,17 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locker {
|
.lockers {
|
||||||
display: grid;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locker {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locker[data-columns="2"] {
|
.locker[data-columns="2"] {
|
||||||
@ -33,47 +40,49 @@
|
|||||||
grid-template-columns: repeat(10, min-content);
|
grid-template-columns: repeat(10, min-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.locker .door {
|
.locker .door {
|
||||||
background-color: hsl(24, 100%, 25%);
|
background-color: hsl(24, 100%, 25%);
|
||||||
border: 0.2rem solid black;
|
border: 0.2rem solid black;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
width: 7rem;
|
width: 7rem;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
}
|
}
|
||||||
.locker .door:not([data-rentals="0"]) {
|
.locker .door:not([data-rentals="0"]) {
|
||||||
background-color: hsl(24, 15%, 25%);;
|
background-color: hsl(24, 15%, 25%);;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% for bank in locker_banks %}
|
{% for bank in locker_banks %}
|
||||||
<div class="locker-bank">
|
<div class="locker-bank" data-bank="{{ bank.slug }}">
|
||||||
<h2>{{ bank.name }}</h2>
|
<h2>{{ bank.name }}</h2>
|
||||||
<div>{{ bank.location }}</div>
|
<div>{{ bank.location }}</div>
|
||||||
<div class="locker"
|
<div class="lockers">
|
||||||
data-columns="{{ bank.columns }}"
|
{% for unit in bank.units.all %}
|
||||||
data-bank="{{ bank.slug }}">
|
<div class="locker" data-columns="{{ unit.columns }}">
|
||||||
{% for row, column, rentals in bank.iter_doors %}
|
{% for row, column, rentals in unit.iter_doors %}
|
||||||
<div class="door"
|
<div class="door"
|
||||||
data-rentals="{{ rentals|length }}"
|
data-rentals="{{ rentals|length }}"
|
||||||
data-row="{{ row }}"
|
data-row="{{ row }}"
|
||||||
data-column="{{ column }}">
|
data-column="{{ column }}">
|
||||||
<h3 class="locker-name">{{ bank.initial }}{{ row }}{{ column }}</h3>
|
<h3 class="locker-name">{{ bank.initial }}{{ row }}{{ column }}</h3>
|
||||||
<div class="locker-status">
|
<div class="locker-status">
|
||||||
{% if rentals|length > 0 %}
|
{% if rentals|length > 0 %}
|
||||||
{# TODO: Should check for more specific permission #}
|
{# TODO: Should check for more specific permission #}
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
{% for rental in rentals %}{{ rental.user }}{% endfor %}
|
{% for rental in rentals %}{{ rental.user }}{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
Occupied
|
Occupied
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
Empty
|
Empty
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user