Skip to content

Commit

Permalink
add custom save validations
Browse files Browse the repository at this point in the history
  • Loading branch information
SKairinos committed Dec 11, 2023
1 parent 15675af commit 1a39619
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 6 deletions.
6 changes: 3 additions & 3 deletions codeforlife/user/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.2.20 on 2023-12-11 14:36
# Generated by Django 3.2.20 on 2023-12-11 17:42

from django.conf import settings
import django.core.validators
Expand Down Expand Up @@ -60,7 +60,7 @@ class Migration(migrations.Migration):
('id', models.CharField(editable=False, help_text='Uniquely identifies a class.', max_length=5, primary_key=True, serialize=False, validators=[django.core.validators.MinLengthValidator(5), django.core.validators.RegexValidator(code='id_not_upper_alphanumeric', message='ID must be alphanumeric with upper case characters.', regex='^[0-9A-Z]*$')], verbose_name='identifier')),
('name', models.CharField(max_length=200, verbose_name='name')),
('read_classmates_data', models.BooleanField(default=False, help_text="Designates whether students in this class can see their fellow classmates' data.", verbose_name='read classmates data')),
('receive_requests_until', models.DateTimeField(blank=True, help_text="A point in the future until which the class can receive requests from students to join. Set to null if it's not accepting requests.", null=True, verbose_name='accept student join requests until')),
('receive_requests_until', models.DateTimeField(help_text="A point in the future until which the class can receive requests from students to join. Set to null if it's not accepting requests.", null=True, verbose_name='accept student join requests until')),
],
options={
'verbose_name': 'class',
Expand Down Expand Up @@ -99,7 +99,7 @@ class Migration(migrations.Migration):
('session_key', models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name='session key')),
('session_data', models.TextField(verbose_name='session data')),
('expire_date', models.DateTimeField(db_index=True, verbose_name='expire date')),
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'session',
Expand Down
1 change: 0 additions & 1 deletion codeforlife/user/models/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ class Manager(WarehouseModel.Manager["Class"]):
receive_requests_until = models.DateTimeField(
_("accept student join requests until"),
null=True,
blank=True,
help_text=_(
"A point in the future until which the class can receive requests"
" from students to join. Set to null if it's not accepting"
Expand Down
1 change: 0 additions & 1 deletion codeforlife/user/models/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class Session(AbstractBaseSession):
] = models.OneToOneField( # type: ignore[assignment]
"user.User",
null=True,
blank=True,
on_delete=models.CASCADE,
)

Expand Down
20 changes: 19 additions & 1 deletion codeforlife/user/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _create_user(
password=make_password(password),
email=email,
)
user.save(using=self._db)
user.save(using=self._db, _from_manager=True)
return user

def create_user(self, password: str, first_name: str, **fields):
Expand Down Expand Up @@ -261,3 +261,21 @@ def is_authenticated(self):
return not self.session.session_auth_factors.exists()
except _session.Session.DoesNotExist:
return False

def save(self, *args, **kwargs):
if self.id is None and not kwargs.pop("_from_manager", False):
raise IntegrityError("Must call create_user instead.")

if (
self.student
# pylint: disable-next=no-member
and self.student.klass.students.filter(
user__first_name=self.first_name
).exists()
):
raise IntegrityError(
"Another student in the class already has first name"
f' "{self.first_name}".'
)

super().save(*args, **kwargs)
32 changes: 32 additions & 0 deletions codeforlife/user/tests/models/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,41 @@ class TestUser(ModelTestCase[User]):
def setUp(self):
self.klass__AB123 = Class.objects.get(pk="AB123")
self.school__1 = School.objects.get(pk=1)
self.student__1 = Student.objects.get(pk=1)

# TODO: test docstrings.

def test_save__create(self):
"""
Cannot create a user calling save.
"""

with self.assert_raises_integrity_error():
User(
first_name="first_name",
last_name="last_name",
email="[email protected]",
password="password",
).save()

def test_save__first_name__student(self):
"""
Students must have a unique name per class.
"""

student = Student.objects.create(
auto_gen_password="password",
klass=self.student__1.klass,
school=self.student__1.school,
)

with self.assert_raises_integrity_error():
User.objects.create_user(
first_name=self.student__1.user.first_name,
password="password",
student=student,
)

def test_constraints__profile(self):
"""
Cannot be a student and a teacher.
Expand Down

0 comments on commit 1a39619

Please sign in to comment.