Related
Using Django 1.10 I'd like to allow the \ character in a username because I'm working in a Windows environment utilizing 'django.contrib.auth.middleware.RemoteUserMiddleware', and the remote user comes in as domain\username (I have no desire to drop the domain as it is used in some business logic).
I can change django\contrib\auth\validators.py easily enough and have the desired affect by amending the line regex = r'^[\w.#+-]+$' to be regex = r'^[\w.#+-\\]+$' however, I thought one could override this class easily but I failed.
I've found some useful links (and many other similar here on SO):
https://stackoverflow.com/a/39820162/4872140
https://stackoverflow.com/a/1214660/4872140
https://docs.djangoproject.com/en/1.10/releases/1.10/#official-support-for-unicode-usernames
https://docs.djangoproject.com/en/1.10/ref/contrib/auth/#django.contrib.auth.models.User.username_validator
But the info is dated, or doesn't quite show exactly/completely how to solve my issue (in the case of the last two). I'm well into the app so changing the AUTH_USER_MODEL is not attractive.
settings.py:
AUTH_USER_MODEL = 'myapp.MyUser'
I tried anyway, thinking I may be able to use proxy on the User model like the following which results in the error "cannot proxy the swapped model 'myapp.DomainUser'":
class DomainASCIIUsernameValidator(ASCIIUsernameValidator):
regex = r'^[\w.#+-\\]+$'
class DomainUser(User):
username_validator = DomainASCIIUsernameValidator()
class Meta:
proxy = True
Is there a way to replace the regex in ASCIIUsernameValidator (and UnicodeUsernameValidator) in a way that the User model stays as is. If you subclass the user model as described at https://docs.djangoproject.com/en/1.10/ref/contrib/auth/#django.contrib.auth.models.User.username_validator are you stuck with specifying that in AUTH_USER_MODEL?
I've read through the discussion at https://groups.google.com/forum/#!topic/django-developers/MBSWXcQBP3k/discussion and feel like creating a custom user from the start may be way to go as a default case.
It took me an entire day and many different answers to get this working. I split up the code into the different parts. First, we have our validator:
validators.py
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.utils.translation import ugettext_lazy as _
class DomainUnicodeUsernameValidator(UnicodeUsernameValidator):
"""Allows \."""
regex = r'^[\w.#+-\\]+$'
message = _(
'Enter a valid username. This value may contain only letters, '
'numbers, and \/#/./+/-/_ characters.'
)
I added changes for the message to also say \ was allowed. You can also easily add DomainASCIIUsernameValidator.
models.py
from django.contrib.auth.models import User
from .validators import DomainUnicodeUsernameValidator
class DomainUser(User):
class Meta:
proxy = True
def __init__(self, *args, **kwargs):
self._meta.get_field(
'username'
).validators[0] = DomainUnicodeUsernameValidator()
super().__init__(*args, **kwargs)
This grabs the validator we set up before. It also proxys the default user and sets the validator for the username field. You can set the validator as a list to include the new validator and the max length validator (which is probably safer in case the order changes).
forms.py
from django.contrib.auth.forms import UserChangeForm
from django.utils.translation import ugettext_lazy as _
from .models import DomainUser
class DomainUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = DomainUser
help_texts = {
'username': _('Required. 150 characters or fewer. Letters, digits and \/#/./+/-/_ only.'), # NOQA
}
Here, I am extending the UserChangeForm and setting the model to the proxy model with the username validation change.
If you would like to allow for users to add user names with \, then you will also need to to change the UserAddForm in a similar way.
Lastly, in admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from .forms import DomainUserChangeForm
class DomainUserAdmin(UserAdmin):
form = DomainUserChangeForm
admin.site.unregister(User)
admin.site.register(User, DomainUserAdmin)
We set the form for our admin that will be used in the django admin pages. Then unset the User admin and replace it with our custom DomainUserAdmin.
Good evening, I am using django-registration for a Web application; By using this, the form that has default django-registration only has 4 fields (username, email, password, repeat password). How to add more fields to this form and store them in the database correctly? Thank you.
I haven't used django-registrations but I found a link which might be helpful to you.
All you need to do is extend the class 'RegistrationForm' and add more fields.
Something like this:
forms.py
import from registration.forms import RegistrationForm
class CustomRegistrationForm(RegistrationForm):
extra_field = forms.EmailField()
extra_field_2 ...
Then, handle the form POST and save these details.
http://django-registration.readthedocs.io/en/2.0.1/forms.html
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)
Is there anything wrong with running alter table on auth_user to make username be varchar(75) so it can fit an email? What does that break if anything?
If you were to change auth_user.username to be varchar(75) where would you need to modify django? Is it simply a matter of changing 30 to 75 in the source code?
username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and #/./+/-/_ characters"))
Or is there other validation on this field that would have to be changed or any other repercussions to doing so?
See comment discussion with bartek below regarding the reason for doing it.
Edit: Looking back on this after many months. For anyone who doesn't know the premise: Some apps don't have a requirement or desire to use a username, they use only email for registration & auth. Unfortunately in django auth.contrib, username is required. You could start putting emails in the username field, but the field is only 30 char and emails may be long in the real world. Potentially even longer than the 75 char suggested here, but 75 char accommodates most sane email addresses. The question is aimed at this situation, as encountered by email-auth-based applications.
There's a way to achieve that without touching the core model, and without inheritance, but it's definitely hackish and I would use it with extra care.
If you look at Django's doc on signals, you'll see there's one called class_prepared, which is basically sent once any actual model class has been created by the metaclass. That moment is your last chance of modifying any model before any magic takes place (ie: ModelForm, ModelAdmin, syncdb, etc...).
So the plan is simple, you just register that signal with a handler that will detect when it is called for the User model, and then change the max_length property of the username field.
Now the question is, where should this code lives? It has to be executed before the User model is loaded, so that often means very early. Unfortunately, you can't (django 1.1.1, haven't check with another version) put that in settings because importing signals there will break things.
A better choice would be to put it in a dummy app's models module, and to put that app on top of the INSTALLED_APPS list/tuple (so it gets imported before anything else). Here is an example of what you can have in myhackishfix_app/models.py :
from django.db.models.signals import class_prepared
def longer_username(sender, *args, **kwargs):
# You can't just do `if sender == django.contrib.auth.models.User`
# because you would have to import the model
# You have to test using __name__ and __module__
if sender.__name__ == "User" and sender.__module__ == "django.contrib.auth.models":
sender._meta.get_field("username").max_length = 75
class_prepared.connect(longer_username)
That will do the trick.
A few notes though:
You might want to change also the help_text of the field, to reflect the new maximum length
If you want to use the automatic admin, you will have to subclass UserChangeForm, UserCreationForm and AuthenticationForm as the maximum length is not deduced from the model field, but directly in the form field declaration.
If you're using South, you can create the following migration to change the column in the underlying database:
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'User.username'
db.alter_column('auth_user', 'username', models.CharField(max_length=75))
def backwards(self, orm):
# Changing field 'User.username'
db.alter_column('auth_user', 'username', models.CharField(max_length=35))
models = {
# ... Copy the remainder of the file from the previous migration, being sure
# to change the value for auth.user / usename / maxlength
Based on Clément and Matt Miller's great combined answer above, I've pulled together a quick app that implements it. Pip install, migrate, and go. Would put this as a comment, but don't have the cred yet!
https://github.com/GoodCloud/django-longer-username
EDIT 2014-12-08
The above module is now deprecated in favor of https://github.com/madssj/django-longer-username-and-email
Updated solution for the Django 1.3 version (without modifying manage.py):
Create new django-app:
monkey_patch/
__init__.py
models.py
Install it as first: (settings.py)
INSTALLED_APPS = (
'monkey_patch',
#...
)
Here is models.py:
from django.contrib.auth.models import User
from django.core.validators import MaxLengthValidator
NEW_USERNAME_LENGTH = 300
def monkey_patch_username():
username = User._meta.get_field("username")
username.max_length = NEW_USERNAME_LENGTH
for v in username.validators:
if isinstance(v, MaxLengthValidator):
v.limit_value = NEW_USERNAME_LENGTH
monkey_patch_username()
The solutions above do seem to update the model length. However, to reflect your custom length in admin, you also need to override the admin forms (frustratingly, they don't simply inherit the length from the model).
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
UserChangeForm.base_fields['username'].max_length = NEW_USERNAME_LENGTH
UserChangeForm.base_fields['username'].widget.attrs['maxlength'] = NEW_USERNAME_LENGTH
UserChangeForm.base_fields['username'].validators[0].limit_value = NEW_USERNAME_LENGTH
UserChangeForm.base_fields['username'].help_text = UserChangeForm.base_fields['username'].help_text.replace('30', str(NEW_USERNAME_LENGTH))
UserCreationForm.base_fields['username'].max_length = NEW_USERNAME_LENGTH
UserCreationForm.base_fields['username'].widget.attrs['maxlength'] = NEW_USERNAME_LENGTH
UserCreationForm.base_fields['username'].validators[0].limit_value = NEW_USERNAME_LENGTH
UserCreationForm.base_fields['username'].help_text = UserChangeForm.base_fields['username'].help_text.replace('30', str(NEW_USERNAME_LENGTH))
As far as I know one can override user model since Django 1.5 which will solve a problem. Simple example here
If you simply modify the database table, you'll still have to deal with Django's validation, so it won't let you make one over 30 characters anyways. Additionally, the username validates so that it can't have special characters like # so simply modifying the length of the field wouldn't work anyways. My bad, looks like it handles that. Here's the username field from models.py in django.contrib.auth:
username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and #/./+/-/_ characters"))
Creating email auth is not hard. Here's a super simple email auth backend you can use. Al l you need to do after that is add some validation to ensure the email address is unique and you're done. That easy.
Yes, it can be done. At least I think this should work; I wound up replacing the whole auth model, so am ready to be corrected if this doesn't work out...
If you have no user records you care about:
drop the auth_user table
change username to max_length=75 in the model
syncdb
If you have user records you need to retain then it's more complicated as you need to migrate them somehow. Easiest is backup and restore of the data from old to new table, something like:
backup the user table data
drop the table
syncdb
reimport user data to the new table; taking care to restore the original id values
Alternatively, using your mad python-django skillz, copy the user model instances from old to new and replace:
create your custom model and temporarily stand it alongside the default model
write a script which copies the instances from the default model to the new model
replace the default model with your custom one
The latter is not as hard as it sounds, but obviously involves a bit more work.
Fundamentally, the problem is that some people want to use an email address as the unique identifier, while the user authentication system in Django requires a unique username of at most 30 characters. Perhaps that will change in the future, but that's the case with Django 1.3 as I'm writing.
We know that 30 characters is too short for many email addresses; even 75 characters is not enough to represent some email addresses, as explained in What is the optimal length for an email address in a database?.
I like simple solutions, so I recommend hashing the email address into a username that fits the restrictions for usernames in Django. According to User authentication in Django, a username must be at most 30 characters, consisting of alphanumeric characters and _, #, +, . and -. Thus, if we use base-64 encoding with careful substitution of the special characters, we have up to 180 bits. So we can use a 160-bit hash function like SHA-1 as follows:
import hashlib
import base64
def hash_user(email_address):
"""Create a username from an email address"""
hash = hashlib.sha1(email_address).digest()
return base64.b64encode(hash, '_.').replace('=', '')
In short, this function associates a username for any email address. I'm aware that there is a tiny probability of a collision in the hash function, but this should not be an issue in most applications.
Just adding the below code at the bottom of settings.py
from django.contrib.auth.models import User
User._meta.get_field("username").max_length = 75
C:...\venv\Lib\site-packages\django\contrib\auth\models.py
first_name = models.CharField(_('first name'), max_length=30, blank=True)
change to
first_name = models.CharField(_('first name'), max_length=75, blank=True)
save
and change in the database
I am using django 1.4.3 which makes it pretty easy and I did not have to change anything else in my code after realising I wanted to use long email addresses as usernames.
If you have direct access to the database, change it there to the amount of characters you would like to, in my case 100 characters.
In your app model (myapp/models.py) add the following
from django.contrib.auth.models import User
class UserProfile(models.Model):
# This field is required.
User._meta.get_field("username").max_length = 100
user = models.OneToOneField(User)
Then in your settings.py you specify the model:
AUTH_USER_MODEL = 'myapp.UserProfile'
If you are using venv (virtual environment), the simplest solution probably is just update the core code directly, i.e. opening the following two files:
- - venv/lib/python2.7/sites-packages/django/contrib/auth/model.py
- venv/lib/python2.7/sites-packages/django/contrib/auth/forms.py
Search for all username field and change max_length from 30 to 100. It is safe since you are already using venv so it won't affect any other Django project.
The best solution is to use email field for email and the username for username.
In the input login form validation, find whether the data is username or the email and if email, query the email field.
This only requires monkey patching the contrib.auth.forms.login_form which is a few lines in the corresponding view.
And it is far better than trying to modify the models and the database tables.
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.