Related
I have model with a field validator
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
class MyModel(model.Model):
name = models.CharField()
size = models.IntegerField(validators=[MinValueValidator(1),MaxValueValidator(10)])
The validator is working well in the django admin panel ,while I try to enter the value more than 10, it's showing me the error message 'Ensure this value is less than or equal to 10' and does not allow to save.
But, when I try in the django shell, the validator is not working, it allows to save the record, I don't know why is the validator not throwing error message here.
>>>form app.models import MyModel
>>>MyModel.objects.create(name="Some Name", size=15)
<MyModel: Some Name>
Can you please suggest me if anything I missed or any mistake i did here. Kindly help me to solve this problem, it will be very greatfull for me, Thanks in advance.
Django validation is mostly application level validation and not validation at DB level. Also Model validation is not run automatically on save/create of the model. If you want to validate your values at certain time in your code then you need to do it manually.
For example:
from django.core.exceptions import ValidationError
form app.models import MyModel
instance = MyModel(name="Some Name", size=15)
try:
instance.full_clean()
except ValidationError:
# Do something when validation is not passing
else:
# Validation is ok we will save the instance
instance.save()
More info you can see at django's documentation https://docs.djangoproject.com/en/1.10/ref/models/instances/#validating-objects
In administration it works automatically because all model forms (ModelForm) will run model validation process alongside form validation.
If you need to validate data because it is coming from untrusted source (user input) you need to use ModelForms and save the model only when the form is valid.
The validator only works when you are using models in a ModelForm.
https://docs.djangoproject.com/en/dev/ref/validators/#how-validators-are-run
You can perform model validation by overidding clean() and full_clean() methods
Validators work only with the Forms and model forms. Can't be used with the model definition because it runs at the app side not the DB side.
You can add this to your model and call it in save().
def save(self, *args, **kwargs):
self.run_validators()
super().save(*args, **kwargs)
def run_validators(self) -> None:
for field_name, field_value in model_to_dict(self).items():
model_field = getattr(UserSearchHistory, field_name)
field = getattr(model_field, 'field', object())
validators = getattr(field, 'validators', list())
for validator_func in validators:
if field_value is not None:
validator_func(field_value)
From django documentation:
Note that validators will not be run automatically when you save a
model, but if you are using a ModelForm, it will run your validators
on any fields that are included in your form.
https://docs.djangoproject.com/en/3.1/ref/validators/#how-validators-are-run
I ran into the same issue.
So the validators only work when you are using Forms and Model Form to fill it.
However, by creating in shell, you probably wanted to test the validators before going live.
So here is the additional piece of code to help in validating the validators.
>>>form app.models import MyModel
>>>MyModel.size.field.run_validators(value=<undesirable value>)
You can not run validator in creating you must run validation in instance if not exception occurred you must save it
It is worth mentioning that model field validators
like validate_color in here:
bg_color = models.CharField(
max_length=50, default="f0f2f5", validators=[validate_color]
)
work with restf_ramework (drf) Serializer class either.
https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py
so validators run when you call is_valid on ModelForm (from django) or is_valid on Serializer (from rest_framework).
I would want to make register form and check if the
user's email is a new one by checking the DB.
But the problem is that I don't know how to make it.
I tried using unique=True to Field and clean_email() method.
I have 2 questions.
1) When ValidationError happens at the step of unique=True,
how could I catch that error? should I override the clean_field() method
of Model? And How could I make it?
2) how could I write efficient and nice code for this registration app?
Below are the source code i made.
# models.py
from django.db import models
class User(models.Model):
university = models.ForeignKey('University')
mail = models.EmailField(unique=True)
password = models.CharField()
nickname = models.CharField()
class University(models.Model):
name = models.CharField()
mail = models.CharField()
from django.forms import ModelForm
class LoginForm(ModelForm):
class Meta:
model = User
fields = ('mail', 'password')
class RegisterForm(ModelForm):
class Meta:
model = User
def clean_mail():
data = self.cleaned_data['mail']
if User.objects.filter(mail=data).exists():
raise forms.ValidationError("This email is already exists")
# views.py
def register(request):
if request.method == 'POST':
###
if form.is_valid():
I am so confused to make this.
I am waiting for a help Thanks in advance ;)
You don't catch that error. Django does it for you: when you call form.is_valid(), it calls each of the clean_field methods and catches any ValidationError exceptions, putting them into the form.errors attribute.
So, what you have is already fine.
Except for the major, huge, absolutely vital issue that you should never store passwords in plain text. Don't create your own standalone user class: inherit from AbstractBaseUser and use Django's built-in methods to create users and hash their passwords. You must not do it like this.
First of All, Your clean_mail() should return self.cleaned_data['mail'] , if error is not raised. Although clean_mail is not required here
Secondly, Django will automatically check for uniqueness of the Email if you set unique=Ture in Model. An error would be raised if it is not unique.
You can over-ride unique error text by providing that in ModelForm's Meta options.
e.g.
class RegisterForm(ModelForm):
class Meta:
model = User
error_messages = {
'mail': {
'unique': _("This email already exists"),
},
}
Note: error_messages option has been added in django v1.6 only...
For previous versions:
You can over-ride the field mail in the ModelForm directly. e.g Define at top of the ModelForm
class RegisterForm(ModelForm):
mail = forms.EmailField(max_length=255, error_messages = { 'unique': 'xyz....',})
class Meta:.......
you can find reference here:https://docs.djangoproject.com/en/1.5/ref/forms/fields/#error-messages
Take a quick look at the following page: https://docs.djangoproject.com/en/dev/ref/models/instances/
The section "Validating objects" guides you through the steps in model validation.
If you require single-column uniqueness on User.mail, simply add the keyword unique=True to the Charfield. The stock implementation of validate_unique() will handle this.
I don't understand this behaviour. Let's say I open a Django shell and type:
from django.contrib.auth.models import User
user = User.objects.create(username="toto", email="titi")
Why does Django let me create this user (with an invalid email) without raising an error?
I have the same "no verification behaviour" creating a user in a POST in my API created with tastypie.
The question is:
As Django does not seem to check this by itself, where am I supposed to perform this kind of verifications sothat I don't have to write them several times (since a user can be created in several ways like website, API, etc.)?
Thanks.
Django doesn't implicitly do any validation if you just call .create() or .save() - you need to explicitly use model validation, or save the object via a
ModelForm. Your example with model validation would look like this:
user = User(username="toto", email="titi")
try:
user.full_clean()
except ValidationError as e:
# handle the error...
pass
user.save()
Or using a ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
f = UserForm(dict(username="toto", email="titi"))
if f.is_valid():
user = f.save()
else:
# handle error, ...
pass
Both model validation and ModelForms invoke the model field's validators, so in the case of the User's email, no additional work is needed for validation. If you need to do custom validation, you can do this in the ModelForm class - it is common to have a forms.py file in the app as a central place for Forms and ModelForms.
The same goes for Tastypie - the default configuration assumes the data submitted is valid. You can override this with the FormValidation class, which uses a Django Form or ModelForm for its validation. A full example would look something like this:
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
validation = FormValidation(form_class=UserForm) # UserForm from above
# more options here....
I'm currently using the default EmailField attribute on my form. The issue I'm running into is that the form considers an invalid email such as name#mail.56 to be valid. Do I need to implement my own validators on this field to make it work correctly?
I was under the impression that having:
#models.py
email = models.EmailField(max_length=254, blank=False, unique=True,
error_messages={'required': 'Please provide your email address.',
'unique': 'An account with this email exist.'},)
Or having:
#forms.py
email = forms.EmailField()
will take care of this type of validation for me but it doesn't seem so.
Indeed, name#mail.56 email is a valid email for django EmailValidator, see no errors:
>>> from django.core.validators import validate_email
>>> validate_email("name#mail.56")
>>>
Django (1.5.1) uses the following regular expression for validating email address:
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
# quoted-string, see also https://www.rfc-editor.org/rfc/rfc2822#section-3.2.5
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"'
r')#((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)$)' # domain
r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$'
And it actually follows RFC2822 standard.
If you want to make name#mail.56 fail during validation, you can create your own validator, and add it to EmailField validators with built-in validate_email validator, like this:
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
def custom_validate_email(value):
if <custom_check>:
raise ValidationError('Email format is incorrect')
...
email = models.EmailField(max_length=254, blank=False, unique=True, validators=[validate_email, custom_validate_email)
And, FYI, you can always file a ticket in django ticket system or ask about the issue on django IRC channel (irc://irc.freenode.net/django).
See also: Writing validators.
Hope that helps.
You can see the used regex here.
I think it doesn't discard 100% of the wrong emails. That's why in the docs it says:
Validates that the given value is a valid email address, using a
moderately complex regular expression.
What I understand from this is that it doesn't do a perfect validation due to a design decision (it would be a performance trade-off).
Finally, I'm pretty sure that your example name#mail.56 is a valid email. The domain part of an email address can be an IP (both IPv4 and IPv6) or a hostname. See here for further info in the subject.
For DB level only validation you will have to call full_clean manually.
Three important citations from documentation:
How validators are run
See the form validation for more information on how validators are run
in forms, and Validating objects for how they’re run in models. Note
that validators will not be run automatically when you save a model,
but if you are using a ModelForm, it will run your validators on any
fields that are included in your form. See the ModelForm documentation
for information on how model validation interacts with forms.
Model.clean_fields
The second step full_clean() performs is to call Model.clean(). This
method should be overridden to perform custom validation on your
model.
Model.full_clean
Note that full_clean() will not be called automatically when you call
your model’s save() method. You’ll need to call it manually when you
want to run one-step model validation for your own manually created
models.
from django.db import models
class MyTable(models.Model):
email = models.EmailField(unique=True)
def save(self, *args, **kwargs):
super().full_clean()
super().save(*args, **kwargs)
I need to patch the standard User model of contrib.auth by ensuring the email field entry is unique:
User._meta.fields[4].unique = True
Where is best place in code to do that?
I want to avoid using the number fields[4]. It's better to user fields['email'], but fields is not dictionary, only list.
Another idea may be to open a new ticket and upload a patch with new parameter inside settings.py:
AUTH_USER_EMAIL_UNIQUE = True
Any suggestions on the most correct way to achieve email address uniqueness in the Django User model?
Caution:
The code below was written for an older version of Django (before Custom
User Models were introduced). It contains a race condition, and
should only be used with a Transaction Isolation Level of SERIALIZABLE
and request-scoped transactions.
Your code won't work, as the attributes of field instances are read-only. I fear it might be a wee bit more complicated than you're thinking.
If you'll only ever create User instances with a form, you can define a custom ModelForm that enforces this behavior:
from django import forms
from django.contrib.auth.models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
if email and User.objects.filter(email=email).exclude(username=username).exists():
raise forms.ValidationError(u'Email addresses must be unique.')
return email
Then just use this form wherever you need to create a new user.
BTW, you can use Model._meta.get_field('field_name') to get fields by name, rather than by position. So for example:
# The following lines are equivalent
User._meta.fields[4]
User._meta.get_field('email')
UPDATE
The Django documentation recommends you use the clean method for all validation that spans multiple form fields, because it's called after all the <FIELD>.clean and <FIELD>_clean methods. This means that you can (mostly) rely on the field's value being present in cleaned_data from within clean.
Since the form fields are validated in the order they're declared, I think it's okay to occasionally place multi-field validation in a <FIELD>_clean method, so long as the field in question appears after all other fields it depends on. I do this so any validation errors are associated with the field itself, rather than with the form.
What about using unique_together in a "different" way? So far it works for me.
class User(AbstractUser):
...
class Meta(object):
unique_together = ('email',)
Simply use below code in models.py of any app
from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True
In settings module:
# Fix: username length is too small,email must be unique
from django.contrib.auth.models import User, models
User._meta.local_fields[1].__dict__['max_length'] = 75
User._meta.local_fields[4].__dict__['_unique'] = True
It's amazing, but I found a best solution for me!
django-registration have form with checking uniqueness of email field: RegistrationFormUniqueEmail
example of usage here
Your form should look something like this.
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
print User.objects.filter(email=email).count()
if email and User.objects.filter(email=email).count() > 0:
raise forms.ValidationError(u'This email address is already registered.')
return email
To ensure a User, no matter where, be saved with a unique email, add this to your models:
#receiver(pre_save, sender=User)
def User_pre_save(sender, **kwargs):
email = kwargs['instance'].email
username = kwargs['instance'].username
if not email: raise ValidationError("email required")
if sender.objects.filter(email=email).exclude(username=username).count(): raise ValidationError("email needs to be unique")
Note that this ensures non-blank email too. However, this doesn't do forms validation as would be appropriated, just raises an exception.
Django has a Full Example on its documentation on how to substitute and use a Custom User Model, so you can add fields and use email as username.
One possible way to do this is to have a pre-save hook on the User object and reject the save of the email already exists in the table.
I think that the correct answer would assure that uniqueness check was placed inside the database (and not on the django side). Because due to timing and race conditions you might end with duplicate emails in the database despite having for example pre_save that does proper checks.
If you really need this badly I guess you might try following approach:
Copy User model to your own app, and change field email to be unique.
Register this user model in the admin app (using admin class from django.contrib.auth.admin)
Create your own authentication backend that uses your model instead of django one.
This method won't make email field unique at the database level, but it's worth trying.
Use a custom validator:
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
def validate_email_unique(value):
exists = User.objects.filter(email=value)
if exists:
raise ValidationError("Email address %s already exists, must be unique" % value)
Then in forms.py:
from django.contrib.auth.models import User
from django.forms import ModelForm
from main.validators import validate_email_unique
class UserForm(ModelForm):
#....
email = forms.CharField(required=True, validators=[validate_email_unique])
#....
Add the below function in any of the models.py file. Then run makemigrations and migrate. Tested on Django1.7
def set_email_as_unique():
"""
Sets the email field as unique=True in auth.User Model
"""
email_field = dict([(field.name, field) for field in MyUser._meta.fields])["email"]
setattr(email_field, '_unique', True)
#this is called here so that attribute can be set at the application load time
set_email_as_unique()
Since version 1.2 (May 11th, 2015) there has been a way to dynamically import any chosen registration form using the settings option REGISTRATION_FORM.
So, one could use something like this:
REGISTRATION_FORM = 'registration.forms.RegistrationFormUniqueEmail'
This is documented here.
And here's the link to the changelog entry.
Django does not allow direct editing User object but you can add pre_save signal and achieve unique email. for create signals u can follow https://docs.djangoproject.com/en/2.0/ref/signals/. then add the following to your signals.py
#receiver(pre_save, sender=User)
def check_email(sender,instance,**kwargs):
try:
usr = User.objects.get(email=instance.email)
if usr.username == instance.username:
pass
else:
raise Exception('EmailExists')
except User.DoesNotExist:
pass
Add somewhere this:
User._meta.get_field_by_name('email')[0]._unique = True
and then execute SQL similar to this:
ALTER TABLE auth_user ADD UNIQUE (email);
The first answer here is working for me when I'm creating new users, but it fails when I try to edit a user, since I am excluding the username from the view. Is there a simple edit for this that will make the check independent of the username field?
I also tried including the username field as a hidden field (since I don't want people to edit it), but that failed too because django was checking for duplicate usernames in the system.
(sorry this is posted as an answer, but I lack the creds to post it as a comment. Not sure I understand Stackoverflow's logic on that.)
You can use your own custom user model for this purpose. You can use email as username or phone as username , can have more than one attribute.
In your settings.py you need to specify below settings
AUTH_USER_MODEL = 'myapp.MyUser'.
Here is the link that can help you .
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#auth-custom-user
from an User inherited model, redefine the attribute correctly. It should work, as is it's not usefull to have that in django core because it's simple to do.
I went to \Lib\site-packages\django\contrib\auth\models
and in class AbstractUser(AbstractBaseUser, PermissionsMixin):
I changed email to be:
email = models.EmailField(_('email address'), **unique=True**, blank=True)
With this if you try to register with email address already present in the database you will get message: User with this Email address already exists.