diff --git a/fet2020/members/forms.py b/fet2020/members/forms.py index 0e4c16b7..58f3f64a 100644 --- a/fet2020/members/forms.py +++ b/fet2020/members/forms.py @@ -34,7 +34,10 @@ class MemberForm(forms.ModelForm): } help_texts = { - "image": _("Mindestgröße: 150*150 px, Verwendbare Formate: ..."), + "image": _( + "Mindestdimension: 150*150 px, maximale Größe: 10MB, " + "erlaubtes Format: Bildformat" + ), "mailaccount": _("Die Mailadresse mit '@fet.at' angeben."), } @@ -72,6 +75,7 @@ class JobGroupForm(forms.ModelForm): "shortterm": _("Kürzel des Tätigkeitsbereichs"), "description": _("Beschreibung des Tätigkeitsbereichs"), "is_pinned": _( - "Dieser Tätigkeitsbereich soll im Fachschaftsbereich angeheftet werden, damit es sofort ersichtlich ist." + "Dieser Tätigkeitsbereich soll im Fachschaftsbereich angeheftet werden, " + "damit es sofort ersichtlich ist." ), } diff --git a/fet2020/members/migrations/0003_auto_20210506_1808.py b/fet2020/members/migrations/0003_auto_20210506_1808.py new file mode 100644 index 00000000..f922a2bd --- /dev/null +++ b/fet2020/members/migrations/0003_auto_20210506_1808.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1.5 on 2021-05-06 16:08 + +import django.core.validators +from django.db import migrations, models +import easy_thumbnails.fields +import members.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0002_auto_20210412_2158'), + ] + + operations = [ + migrations.AlterField( + model_name='member', + name='image', + field=easy_thumbnails.fields.ThumbnailerImageField(upload_to='uploads/members/image/', validators=[members.validators.validate_file_size, members.validators.validate_image_dimension]), + ), + migrations.AlterField( + model_name='member', + name='mailaccount', + field=models.CharField(error_messages={'unique': 'Diese Mailadresse existiert schon.'}, max_length=128, unique=True, validators=[django.core.validators.EmailValidator(), members.validators.validate_domainonly_email], verbose_name='Mailadresse'), + ), + migrations.AlterField( + model_name='member', + name='phone', + field=models.CharField(blank=True, max_length=17, validators=[members.validators.PhoneNumberValidator()]), + ), + migrations.AlterField( + model_name='member', + name='username', + field=models.CharField(blank=True, max_length=128, verbose_name='fet.at Benutzername'), + ), + ] diff --git a/fet2020/members/models.py b/fet2020/members/models.py index 89f0d245..41111a29 100644 --- a/fet2020/members/models.py +++ b/fet2020/members/models.py @@ -1,12 +1,12 @@ import logging from django.contrib.auth.models import User -from django.core.validators import RegexValidator, ValidationError +from django.core.validators import ValidationError, validate_email from django.db import models from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ - from easy_thumbnails.fields import ThumbnailerImageField + from .managers import ( ActiveJobMemberManager, InactiveJobMemberManager, @@ -14,6 +14,12 @@ from .managers import ( JobGroupManager, MemberManager, ) +from .validators import ( + PhoneNumberValidator, + validate_domainonly_email, + validate_file_size, + validate_image_dimension, +) logger = logging.getLogger(__name__) @@ -24,10 +30,18 @@ class Member(models.Model): nickname = models.CharField("Spitzname", max_length=128) # LDAP Username - username = models.CharField("Benutzername", blank=True, max_length=128) + username = models.CharField("fet.at Benutzername", max_length=128, blank=True) # fet mail account - mailaccount = models.CharField("Mailadresse", unique=True, max_length=128) + mailaccount = models.CharField( + "Mailadresse", + unique=True, + max_length=128, + validators=[validate_email, validate_domainonly_email], + error_messages={ + 'unique': _("Diese Mailadresse existiert schon."), + }, + ) class MemberRole(models.TextChoices): ACTIVE = "A", _("Active") @@ -41,17 +55,19 @@ class Member(models.Model): ) description = models.TextField(null=True, blank=True) - image = ThumbnailerImageField(upload_to="uploads/members/image/") + + image = ThumbnailerImageField( + upload_to="uploads/members/image/", + validators=[validate_file_size, validate_image_dimension], + ) birthday = models.DateField(null=True, blank=True) - phone_error_msg = _( - ( - "Phone number must be entered in the format: +999999999'. Up to 15 digits allowed." - ) + phone = models.CharField( + max_length=17, + blank=True, + validators=[PhoneNumberValidator()], ) - phone_regex = RegexValidator(regex=r"^\+?1?\d{9,15}$", message=phone_error_msg) - phone = models.CharField(validators=[phone_regex], max_length=17, blank=True) address = models.TextField(null=True, blank=True) @@ -70,16 +86,6 @@ class Member(models.Model): if not self.image: raise ValidationError(_("Es fehlt das Profilbild.")) - if self.image.height < 150 or self.image.width < 150: - raise ValidationError( - _("Das Bild ist zu klein. (Höhe: {}, Breite: {})").format( - self.image.height, self.image.width - ) - ) - - if not "@fet.at" in self.mailaccount: - raise ValidationError(_("In der Mailadresse fehlt die Domäne.")) - if self.username: try: user = User.objects.get(username=self.username.lower()) @@ -140,15 +146,15 @@ class Job(models.Model): verbose_name = "Tätigkeit" verbose_name_plural = "Tätigkeiten" + def __str__(self): + return self.name + def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.shortterm) super().save(*args, **kwargs) - def __str__(self): - return self.name - class JobMember(models.Model): member = models.ForeignKey( diff --git a/fet2020/members/tests.py b/fet2020/members/tests.py index fe6fc53d..dbb89903 100644 --- a/fet2020/members/tests.py +++ b/fet2020/members/tests.py @@ -7,6 +7,8 @@ from .models import Member, Job, JobGroup from .forms import MemberForm, JobForm, JobGroupForm +image_path = os.path.join(os.path.dirname(__file__), "tests/files/peter.jpg") + class MemberTestCase(TestCase): def setUp(self): member = Member() @@ -24,12 +26,10 @@ class MemberTestCase(TestCase): class MemberFormTestCase(TestCase): - def test_form(self): + def test_basic_form(self): image = SimpleUploadedFile( name="peter.jpg", - content=open( - os.path.join(os.path.dirname(__file__), "tests/files/peter.jpg"), "rb" - ).read(), + content=open(image_path, "rb").read(), content_type="image/jpeg", ) @@ -50,6 +50,32 @@ class MemberFormTestCase(TestCase): member = Member.objects.get(firstname="Peter") self.assertEqual(member.__str__(), "Peter Traunmüller") + def test_advanced_form(self): + # test blank fields, like phone number + image = SimpleUploadedFile( + name="peter.jpg", + content=open(image_path, "rb").read(), + content_type="image/jpeg", + ) + + form = MemberForm( + data={ + "firstname": "Peter", + "surname": "Traunmüller", + "nickname": "Pet", + "mailaccount": "peter@fet.at", + "role": "A", + "phone": "+4364412345678", + }, + files={"image": image}, + ) + + self.assertTrue(form.is_valid()) + form.save() + + member = Member.objects.get(firstname="Peter") + self.assertEqual(member.phone, "+4364412345678") + def test_form_error_no_image(self): form = MemberForm( data={ @@ -62,7 +88,84 @@ class MemberFormTestCase(TestCase): ) self.assertFalse(form.is_valid()) - self.assertEqual(form.errors.as_data()['__all__'][0].message, "Es fehlt das Profilbild.") + self.assertEqual( + form.errors.as_data()['__all__'][0].message, + "Es fehlt das Profilbild." + ) + + def test_form_wrong_mailaccount(self): + image = SimpleUploadedFile( + name="peter.jpg", + content=open(image_path, "rb").read(), + content_type="image/jpeg", + ) + + form = MemberForm( + data={ + "firstname": "Peter", + "surname": "Traunmüller", + "nickname": "Pet", + "mailaccount": "peter@feet.at", + "role": "A", + }, + files={"image": image}, + ) + + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors.as_data()['mailaccount'][0].message, + "In der Mailadresse fehlt die richtige Domäne." + ) + + def test_form_wrong_domain(self): + image = SimpleUploadedFile( + name="peter.jpg", + content=open(image_path, "rb").read(), + content_type="image/jpeg", + ) + + form = MemberForm( + data={ + "firstname": "Peter", + "surname": "Traunmüller", + "nickname": "Pet", + "mailaccount": "peterfet.at", + "role": "A", + }, + files={"image": image}, + ) + + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors.as_data()['mailaccount'][0].message, + "Gib eine gültige E-Mail Adresse an." + ) + + def test_form_wrong_phone_number(self): + image = SimpleUploadedFile( + name="peter.jpg", + content=open(image_path, "rb").read(), + content_type="image/jpeg", + ) + + form = MemberForm( + data={ + "firstname": "Peter", + "surname": "Traunmüller", + "nickname": "Pet", + "mailaccount": "peter@fet.at", + "role": "A", + "phone": "+43644+12345678", + }, + files={"image": image}, + ) + + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors.as_data()['phone'][0].message, + "Telefonnummer muss in diesem Format +999999999999 eingegeben werden. " + "Bis zu 15 Zahlen sind erlaubt." + ) class JobGroupFormTestCase(TestCase): diff --git a/fet2020/members/validators.py b/fet2020/members/validators.py new file mode 100644 index 00000000..9d3c3f80 --- /dev/null +++ b/fet2020/members/validators.py @@ -0,0 +1,31 @@ +from django.core.validators import RegexValidator, ValidationError +from django.utils.deconstruct import deconstructible +from django.utils.translation import gettext_lazy as _ + + +@deconstructible +class PhoneNumberValidator(RegexValidator): + regex = r"^\+?1?\d{9,15}$" + message = _( + "Telefonnummer muss in diesem Format +999999999999 eingegeben werden. " + "Bis zu 15 Zahlen sind erlaubt." + ) + + +def validate_domainonly_email(value): + if not "fet.at" in value: + raise ValidationError(_("In der Mailadresse fehlt die richtige Domäne.")) + +def validate_file_size(value): + if value.size > 10 * 1024 * 1024: + raise ValidationError(_("Die maximale Dateigröße ist 10MB.")) + +def validate_image_dimension(value): + if value.height < 150 or value.width < 150: + raise ValidationError( + _("Das Bild ist zu klein. (Höhe: %(height)s, Breite: %(width)s)"), + params={ + "height": value.height, + "width": value.width, + } + )