diff --git a/antispam/forms.py b/antispam/forms.py index 0e327f2a..6576322c 100644 --- a/antispam/forms.py +++ b/antispam/forms.py @@ -2,7 +2,7 @@ from django import forms from django.core.validators import EmailValidator from django.utils.translation import gettext_lazy as _ -from antispam.models import ToxicDomain +from antispam.models.models import ToxicDomain class AntiSpamEmailValidator(EmailValidator): @@ -17,6 +17,6 @@ validate_antispam_email = AntiSpamEmailValidator() class AntiSpamEmailField(forms.EmailField): - """An email field that email addresses with a known toxic domain.""" + """An email field that forbids email addresses with a known toxic domain.""" default_validators = [validate_antispam_email] diff --git a/antispam/management/commands/update_spam_database.py b/antispam/management/commands/update_spam_database.py index 2761c649..d4e40d2c 100644 --- a/antispam/management/commands/update_spam_database.py +++ b/antispam/management/commands/update_spam_database.py @@ -38,22 +38,31 @@ class Command(BaseCommand): return domains def _update_domains(self, domains: set[str]): - # Cleanup database - ToxicDomain.objects.filter(is_externally_managed=True).delete() - - # Create database - ToxicDomain.objects.bulk_create( - [ - ToxicDomain(domain=domain, is_externally_managed=True) - for domain in domains - ], - ignore_conflicts=True, - ) + existing = set(ToxicDomain.objects.values_list("domain", flat=True)) + if existing == domains: + self.stdout.write("Domain database was up to date") + return + if to_remove := (existing - domains): + # Cleanup database + ToxicDomain.objects.filter( + domain__in=to_remove, is_externally_managed=True + ).delete() + if to_create := (domains - existing): + # Create database + ToxicDomain.objects.bulk_create( + [ + ToxicDomain(domain=domain, is_externally_managed=True) + for domain in to_create + ], + ignore_conflicts=True, + ) self.stdout.write("Domain database updated") def handle(self, *args, **options): if not self._should_update(force=options["force"]): - self.stdout.write("Domain database is up to date") + self.stdout.write( + "No update required. Use --force if you want to do it anyway." + ) return self.stdout.write("Updating domain database") diff --git a/antispam/models/__init__.py b/antispam/models/__init__.py new file mode 100644 index 00000000..ab6041d3 --- /dev/null +++ b/antispam/models/__init__.py @@ -0,0 +1,4 @@ +from antispam.models.fields import AntiSpamEmailField +from antispam.models.models import ToxicDomain + +__all__ = ["AntiSpamEmailField", "ToxicDomain"] diff --git a/antispam/models/fields.py b/antispam/models/fields.py new file mode 100644 index 00000000..cc747e0b --- /dev/null +++ b/antispam/models/fields.py @@ -0,0 +1,21 @@ +from django.conf import settings +from django.db import models + +from antispam import forms + + +class AntiSpamEmailField(models.EmailField): + """An email field that forbids email addresses with a known toxic domain.""" + + default_validators = [forms.validate_antispam_email] + + +if settings.TESTING: + from model_bakery import baker + + baker.generators.add( + AntiSpamEmailField, lambda: baker.random_gen.gen_email(max_length=100) + ) +else: # pragma: no cover + # baker is only used in tests, so we don't need coverage for this part + pass diff --git a/antispam/models.py b/antispam/models/models.py similarity index 100% rename from antispam/models.py rename to antispam/models/models.py diff --git a/antispam/tests/__init__.py b/antispam/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/core/models.py b/core/models.py index 7baec8d1..e9bb28a6 100644 --- a/core/models.py +++ b/core/models.py @@ -55,6 +55,7 @@ from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from PIL import Image, ImageOps +from antispam.models import AntiSpamEmailField from core.utils import get_last_promo if TYPE_CHECKING: @@ -158,7 +159,7 @@ class User(AbstractUser): first_name = models.CharField(_("first name"), max_length=64) last_name = models.CharField(_("last name"), max_length=64) - email = models.EmailField(_("email address"), unique=True) + email = AntiSpamEmailField(_("email address"), unique=True) date_of_birth = models.DateField(_("date of birth"), blank=True, null=True) nick_name = models.CharField(_("nick name"), max_length=64, null=True, blank=True) last_update = models.DateTimeField(_("last update"), auto_now=True) @@ -267,7 +268,7 @@ class User(AbstractUser): forum_signature = models.TextField( _("forum signature"), max_length=256, blank=True, default="" ) - second_email = models.EmailField(_("second email address"), null=True, blank=True) + second_email = AntiSpamEmailField(_("second email address"), null=True, blank=True) phone = PhoneNumberField(_("phone"), null=True, blank=True) parent_phone = PhoneNumberField(_("parent phone"), null=True, blank=True) address = models.CharField(_("address"), max_length=128, blank=True, default="") diff --git a/core/views/forms.py b/core/views/forms.py index cc235a2e..cc0af005 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -48,7 +48,6 @@ from django.utils.translation import gettext_lazy as _ from phonenumber_field.widgets import RegionalPhoneNumberWidget from PIL import Image -from antispam.forms import AntiSpamEmailField from core.models import Gift, Group, Page, PageRev, Preferences, SithFile, User from core.utils import resize_image from core.views.widgets.ajax_select import ( @@ -185,10 +184,7 @@ class RegisteringForm(UserCreationForm): class Meta: model = User fields = ("first_name", "last_name", "email", "cgu_approved_at") - field_classes = { - "email": AntiSpamEmailField, - "cgu_approved_at": CGUApprovalField, - } + field_classes = {"cgu_approved_at": CGUApprovalField} class CGUApprovalForm(forms.ModelForm):