Model fields do not enforce their respective constraints

I am new to django and have a question about models and the constraints the fields are supposed to enforce in the database (I am using Mongo). I built the following model for the user class, and want to make id the primary key, and email and password required. After making migrations and migrating to the mongo collection, I am able to make post requests access that data in the views, and then make and save a new user with those invalid emails. My understanding of model fields is that they should enforce certain constraints in the database. For example, models.EmailField should automatically check that the email being passed into is valid. Maybe I am incorrect in this or am missing something.

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        '''
        Create and save a user with the given email, and password.
        '''
        if not email:
            raise ValueError('The given email must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(
                'Superuser must have is_staff=True.'
            )
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(
                'Superuser must have is_superuser=True.'
            )

        return self._create_user(email, password, **extra_fields)

    class Meta:
        managed = False
class User(AbstractBaseUser, PermissionsMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(
        unique=True,
        max_length=255,
        blank=False,
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into '
            'this admin site.'
        ),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be '
            'treated as active. Unselect this instead '
            'of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(
        _('date joined'),
        default=timezone.now,
    )

    # Add additional fields here if needed

    objects = UserManager()

    USERNAME_FIELD = 'email'

    class Meta:
        db_table = 'user_credentials'

See the docs on EmailField and the referenced link to EmailValidator to see what it checks.

“Validating” email addresses is a different subject than validating an email address field - which can be a rather complex subject itself.

So does EmailField automatically validate the email put into it?

For some definition of “validate”, yes. There’s still the open question of what exactly you mean by validating an email. In Django’s case, it can (somewhat) validate the structure of the field, but not the content.

If you’re using a ModelForm, the validators are run when the form is submitted.

See the docs on Validating objects for using them (validators) in other circumstances.

The only way to validate an email is to send a mail to it and ask the user to click a unique confirmation link. There really isn’t any other way that’s going to work in all circumstances.

Not sure exactly how Django validates email fields, but it’s going to be strictly a formatting/regex kind of validation, so that the mail is something like xxx@yyy.zzz - if you google email validation you’ll get a number of different regex patterns that people have used to do this depending on their needs :slight_smile:

If you need a more specific email validation (eg. that all emails match your company domain), then you’ll need to write a custom validator, probably one that inherits from the standard django one that you can then add your extra checks to.

My issue is that I can pass in a garbage string into the POST request date field, email field etc, and Django does not recognize that, and allows it to be put into the database.

Are you saying no validation is being done at all?

EmailFeld uses the EmailInput widget and the EmailValidator so this should just happen.

https://docs.djangoproject.com/en/3.2/ref/forms/models/

This is pre-database hit, so I can’t see Mongo should be making a difference, though I do know Mongo and Django is potentially not a great combination and comes with a few problems. Never used it myself.

yeah, it seems like EmailField is not validating at all. It allows me to set a random string as its value. I was wondering if I need to call some method to validate. EmailField does automatically check if there already exists another email in Mongo, so I know that unique =True is working.

The unique=True should be getting applied at the DB level, the Validator should happen before that, in Django itself I think.

Are you using a mongo specific package like djongo?

TBH, at this point, I can only suggest that you step through the code with a debugger and see if that gives you any clues as to why things aren’t working.

The only other thing I can think of is that you’re doing something in your view that short-circuits the validation somehow. Can you post your view code?

Yes, as documented above (Validating objects), if you’re not using a ModelForm or the Model’s save method (or if the MongoDB adapter you’re using doesn’t do this), you need to call the validators yourself using the Model’s full_clean method.