156 lines
5.5 KiB
Python
156 lines
5.5 KiB
Python
import logging
|
|
|
|
from django.contrib.auth.models import User
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
|
|
from utils.models import CampRelatedModel
|
|
|
|
from .dectutils import DectUtils
|
|
|
|
logger = logging.getLogger("bornhack.%s" % __name__)
|
|
dectutil = DectUtils()
|
|
|
|
|
|
class DectRegistration(CampRelatedModel):
|
|
"""
|
|
This model contains DECT registrations for users and services
|
|
"""
|
|
|
|
class Meta:
|
|
unique_together = [("camp", "number")]
|
|
|
|
camp = models.ForeignKey(
|
|
"camps.Camp",
|
|
related_name="dect_registrations",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
|
|
user = models.ForeignKey(
|
|
User,
|
|
on_delete=models.PROTECT,
|
|
help_text="The django user who created this DECT registration",
|
|
)
|
|
|
|
number = models.CharField(
|
|
max_length=9,
|
|
blank=True,
|
|
help_text="The DECT phonenumber, four digits or more. Optional if you specify letters.",
|
|
)
|
|
|
|
letters = models.CharField(
|
|
max_length=9,
|
|
blank=True,
|
|
help_text="The letters or numbers chosen to represent this DECT number in the phonebook. Optional if you specify a number.",
|
|
)
|
|
|
|
description = models.TextField(
|
|
blank=True,
|
|
help_text="Description of this registration, like a name/handle, or a location or service name.",
|
|
)
|
|
|
|
activation_code = models.CharField(
|
|
max_length=10,
|
|
blank=True,
|
|
help_text="The 10 digit numeric activation code",
|
|
)
|
|
|
|
publish_in_phonebook = models.BooleanField(
|
|
default=True,
|
|
help_text="Check to list this registration in the phonebook",
|
|
)
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
This is just here so we get the validation in the admin as well.
|
|
"""
|
|
# validate that the phonenumber and letters are valid and then save()
|
|
self.clean_number()
|
|
self.clean_letters()
|
|
super().save(*args, **kwargs)
|
|
|
|
def clean_number(self):
|
|
"""
|
|
We call this from the views form_valid() so we have a Camp object available for the validation.
|
|
This code really belongs in model.clean(), but that gets called before form_valid()
|
|
which is where we set the Camp object for the model instance.
|
|
"""
|
|
# first check if we have a phonenumber...
|
|
if not self.number:
|
|
# we have no phonenumber, do we have some letters at least?
|
|
if not self.letters:
|
|
raise ValidationError(
|
|
"You must enter either a phonenumber or a letter representation of the phonenumber!"
|
|
)
|
|
# we have letters but not a number, let's deduce the numbers
|
|
self.number = dectutil.letters_to_number(self.letters)
|
|
|
|
# First of all, check that number is numeric
|
|
try:
|
|
int(self.number)
|
|
except ValueError:
|
|
raise ValidationError("Phonenumber must be numeric!")
|
|
|
|
# check for conflicts with the same number
|
|
if (
|
|
DectRegistration.objects.filter(camp=self.camp, number=self.number)
|
|
.exclude(pk=self.pk)
|
|
.exists()
|
|
):
|
|
raise ValidationError(f"The DECT number {self.number} is in use")
|
|
|
|
# check for conflicts with a longer number
|
|
if (
|
|
DectRegistration.objects.filter(
|
|
camp=self.camp, number__startswith=self.number
|
|
)
|
|
.exclude(pk=self.pk)
|
|
.exists()
|
|
):
|
|
raise ValidationError(
|
|
f"The DECT number {self.number} is not available, it conflicts with a longer number."
|
|
)
|
|
|
|
# check if a shorter number is blocking
|
|
i = len(self.number) - 1
|
|
while i:
|
|
if (
|
|
DectRegistration.objects.filter(camp=self.camp, number=self.number[:i])
|
|
.exclude(pk=self.pk)
|
|
.exists()
|
|
):
|
|
raise ValidationError(
|
|
f"The DECT number {self.number} is not available, it conflicts with a shorter number."
|
|
)
|
|
i -= 1
|
|
|
|
def clean_letters(self):
|
|
"""
|
|
We call this from the views form_valid() so we have a Camp object available for the validation.
|
|
This code really belongs in model.clean(), but that gets called before form_valid()
|
|
which is where we set the Camp object for the model instance.
|
|
"""
|
|
# if we have a letter representation of this number they should have the same length
|
|
if self.letters:
|
|
if len(self.letters) != len(self.number):
|
|
raise ValidationError(
|
|
f"Wrong number of letters ({len(self.letters)}) - should be {len(self.number)}"
|
|
)
|
|
|
|
# loop over the digits in the phonenumber
|
|
combinations = list(dectutil.get_dect_letter_combinations(self.number))
|
|
if not combinations:
|
|
raise ValidationError(
|
|
"Numbers with 0 and 1 in them can not be expressed as letters"
|
|
)
|
|
|
|
if self.letters.upper() not in list(combinations):
|
|
# something is fucky, loop over letters to give a better error message
|
|
i = 0
|
|
for digit in self.number:
|
|
if self.letters[i].upper() not in dectutil.DECT_MATRIX[digit]:
|
|
raise ValidationError(
|
|
f"The digit '{digit}' does not match the letter '{self.letters[i]}'. Valid letters for the digit '{digit}' are: {dectutil.DECT_MATRIX[digit]}"
|
|
)
|
|
i += 1
|