Disabling email-style usernames in Django 1.2 with django-registration - django

Django 1.2 allows usernames to take the form of an email address.
Changed in Django 1.2: Usernames may
now contain #, +, . and - characters
I know that's a much-requested feature, but what if you don't want the new behavior? It makes for messy usernames in profile URLs and seems to break django-registration (if a user registers an account with an email-style username, the link in the django-registration activation email returns 404).
Does anyone have a recipe for restoring the old behavior and disabling email-style usernames?

There is no straightforward way to revert back to old behavior.
The easiest way to handle this would be to enforce client and server side validation of usernames according to your demands. django-registration is not an actively developed component, I wouldn't count on anything coming from that direction. Just add some extra validation on your side.
To quote Jacob on this matter:
[...] another common request is to allow the
use of email addresses as usernames.
Custom registration/signup forms can
deal with further restrictions.

django-registration actually wasn't the problem here. The problem was that I had subclassed its RegistrationForm, redefining the username field with new help_text. In so doing, I had prevented it from using its own regex field. To fix this, I had to pull a few bits and pieces from RegistrationForm into my EnhancedRegistrationForm subclass.
Note the regex line, which mirrors the old-style username character restrictions (which is what I want).
from registration.forms import RegistrationForm
# Carry these over from RegistrationForm - needed in the form definition below
attrs_dict = {'class': 'required'}
from django.utils.translation import ugettext_lazy as _
class EnhancedRegistrationForm(RegistrationForm):
first_name = forms.CharField(label='first name', max_length=30, required=True)
last_name = forms.CharField(label='last name', max_length=30, required=True)
username = forms.RegexField(regex=r'^\w+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
help_text='Email addresses cannot be used as usernames.',
required=True,
label=_("Username"),
error_messages={'invalid':"You cannot use an email address as a username, sorry."})
class Meta:
fields = ('first_name','last_name','username','email','password1','password2')
def save(self, *args, **kwargs):
"""
Overriding save, so call the parent form save and return the new_user
object.
"""
new_user = super(EnhancedRegistrationForm, self).save(*args, **kwargs)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
return new_user

Related

Django REST Framework: force lowercase username

I am just starting to play around with Django REST Framework for a side project, and I am trying to force all usernames to be lowercase (since by default they are case sensitive and I don't want people to have usernames like kevin and Kevin for example).
This is the code I am using to create a user:
# The serializer
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
token = serializers.SerializerMethodField()
def create(self, validated_data):
user = get_user_model().objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def get_token(self, user):
token = Token.objects.create(user=user)
return token.key
class Meta:
model = get_user_model()
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'token')
# The View
class RegisterUserView(CreateAPIView):
model = User
serializer_class = UserSerializer
permission_classes = (permissions.AllowAny, )
# And the URL pattern
urlpatterns = [
path(r'user/register/', RegisterUserView.as_view()),
]
So the user can create a new account by posting at least a username and password to user/register, and in the response he'll get the full user object (with first name, last name, email and the auth token). This works.
However, I am struggling with forcing lowercase usernames. For example when I add something like validated_data['username'] = validated_data['username'].lower() to the create function in the serializer, the server simply generates an error 500 "UNIQUE constraint failed: auth_user.username" when trying to create a user with the same username (but different case). That's of course not ideal, that error 500.
I've found some suggestions to add a custom user manager, something like this:
lass MyUserManager(BaseUserManager):
def get_by_natural_key(self, username):
return self.get(username__iexact=username)
But that didn't do anything either, after hooking it up via a custom user class and adding to the settings file. I could still create users with capitals in their username.
What's the simplest solution to get this to simply work?
If you're using a custom user model and want this enforced throughout Django, you can create a custom validator. For example, create a validators.py as a sibling to the models.py which contains your custom user model:
from django.core import validators
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
#deconstructible
class MyUsernameValidator(validators.RegexValidator):
"""
Validator for usernames.
- Only ASCII lowercase letters, numbers and underscore are supported.
- Must start with a letter.
"""
regex = r'^[a-z][a-z0-9_]+$'
message = _(
'Enter a valid username. This value may contain only lowercase ASCII letters, '
'numbers, and underscores. Must start with a letter.'
)
flags = 0
Then in your custom user model, include:
from .validators import MyUsernameValidator
...
class User(AbstractBaseUser, PermissionsMixin):
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = [
'first_name', 'last_name', 'email',
]
username = models.CharField(
max_length=16,
unique=True,
help_text=_(
'Required. 16 characters or fewer. Lowercase letters, digits _ only; must start with a letter.'
),
validators=[MyUsernameValidator()],
error_messages={
'unique': _("A user with that username already exists."),
},
)
There are some slightly simpler methods, but this will enforce it through Django, as long as you stick to the ORM. Good luck!
Probably the reason you are getting "UNIQUE constraint failed: auth_user.username" error is because username validation is run on unprocessed version of the username (i.e with uppercase letters), but when inserting you are trying to create a user with username converted to lowercase. To exemplify, let's say you have a user in the db with the following username:
my_username
Then you are trying to create a user with the following username:
My_Username
This passes validation because there is no user in the db with username "My_Username", but while creating, you are trying to create a user with username "my_username", which exists in the database, so you are getting an IntegrityError.
There are many ways to solve this problem, you can modify the username before passing it to the serializer, so in the serializer context username will always be lowercase. You can also use a custom ModelManager as you tried already, but you'd need to convert the uesrname to lowercase in the manager before saving. Either way, you need to validate your data with the correct version of the username, to do this you can add a validation to your serializer like this:
class UserSerializer(serializers.ModelSerializer):
...
validate_username(self, value):
try:
get_user_model().objects.get(username=value.lower())
except ObjectDoesNotExist:
# Good to go
pass
else:
# Username in use
raise serializers.ValidationError(...)
return value

add a placeholder in these fields django

I'm really confused how to use placeholders. I'm using model that has a couple of fields of data and I want to have a place holder telling he user what to enter. So placeholder sounds like a good way to put this info into the text field. I'm using Crispy forms and fields come from the model.
Here is my forms.py just don't know how to incorporate placeholders here if its even here where I need to do it.
from django import forms
from .models import MRReg
class SignUpForm(forms.ModelForm):
class Meta:
model = MRReg
fields = ['SiteID','HeliumValue']
def clean_HeliumValue(self):
HeliumValue = self.cleaned_data.get('HeliumValue')
#HeliumValue_base, provider = HeliumValue.split("#")
#domain, extension = provider.split('.')
#if not extension == "edu":
#raise forms.ValidationError("Please use a valid .edu address")
return HeliumValue
#print (self.cleaned_data)
def clean_SiteID(self):
SiteID = self.cleaned_data.get('SiteID')
raise forms.ValidationError("Please enter a valid SiteID")
return SiteID
If you use forms widgets like forms.CharField you can pass this to it, for example forms.CharField()
forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'my-placeholder'}))

How enforce filling up the user profile after first social login in django-allauth?

I've created UserProfile model in my application:
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
verbose_name=_('user'),
related_name='user_profile')
locality = models.CharField(max_length=85)
voivodship = models.CharField(max_length=20,
choices=Vovoidship.choices,
validators=[Vovoidship.validator])
postcode = models.IntegerField()
street = models.CharField(max_length=75)
building = models.SmallIntegerField()
premises = models.CharField(max_length=80)
phonenumber = PhoneNumberField(blank=True)
#staticmethod
def post_save_create(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(UserProfile.post_save_create, sender=User)
Now, I felt into my own trap. I don't want to loose constraints and keep the requirement in the database that address fields need to be filled up. I'm using django-allauth. While using the setting ACCOUNT_SIGNUP_FORM_CLASS = 'management.forms.SignupForm' solves the problem for traditional sign up form, if the user logs in first time using the social account I got hit by constraint violation for obvious reasons:
IntegrityError at /accounts/google/login/callback/
null value in column "postcode" violates not-null constraint
DETAIL: Failing row contains (4, , , null, , null, , ).
Hence the question, how to correctly implement the request for filling up the information for fields in the application UserProfile? I'm surprised that django-allauth doesn't have a build in handler for that the same way as ACCOUNT_SIGNUP_FORM_CLASS is done.
As I'm new to Django please assume I rather don't know something than it should be obvious. Thanks.
I think you need to:
1.- Create your custom Signup Class, for you to do the additional work
class SignupForm(forms.Form):
locality = forms.CharField(max_length=85)
voivodship = forms.CharField(max_length=20)
postcode = forms.IntegerField()
etc.
def signup(self, request, user):
# I think the profile will exist at this point based on
# your post_save_create. But if not, just create it here
if user.user_profile:
user.user_profile.locality = self.cleaned_data['locality']
user.user_profile.voivodship = self.cleaned_data['voivodship']
user.user_profile.postcode = self.cleaned_data['postcode']
...
user.user_profile.save()
2.- Set ACCOUNT_SIGNUP_FORM_CLASS = 'yourproject.yourapp.forms.SignupForm' to have allauth use your form
3.- Set SOCIALACCOUNT_AUTO_SIGNUP=False to ensure the form is presented even with social signup.
With some credits to davka I've managed to form a working solution which required creating UserProfile object inside signup() method of the SignupForm class, but because of database/model constrains it has be be filled with data during creation. The result:
class SignupForm(ModelForm):
first_name = CharField()
last_name = CharField()
class Meta:
model = UserProfile
exclude = ['user', 'phonenumber']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
profile, created = UserProfile.objects.get_or_create(
user=user, defaults={
'locality': self.cleaned_data['locality'],
'voivodship': self.cleaned_data['voivodship'],
'postcode': self.cleaned_data['postcode'],
'street': self.cleaned_data['street'],
'building': self.cleaned_data['building'],
'premises': self.cleaned_data['premises'],
})
if created: # This prevents saving if profile already exist
profile.save()
The solution doesn't totally fit into DRY principle, but shows the idea. Going further it could probably iterate over results matching model fields.
Two elements need to be set correctly in settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourapp.forms.SignupForm' to enable this form in allauth
SOCIALACCOUNT_AUTO_SIGNUP = False this - contrary to the intuition - let the allauth display the form before finishing the signup if the user selected social sign in but don't have an account; it works safely if the account already exists (username and/or e-mail address depending on other settings) as just does't allow to finish registration (why they call it sign up?) and the user is forced to log in and link social account.

Password fields rendering as plain text in documentation

I am using Django rest framework 3.1 and django-rest-swagger 0.3.2. Things are working fairly well however I am having an issue with the password input fields in my login serializer. My login serializer is pretty simple, it inherits from rest_framework.authtoken.serializers.AuthTokenSerializer:
class MyLoginSerializer(AuthTokenSerializer):
def validate(self, attrs):
# small amount of validation logic here
The AuthTokenSerializer has the password field defined with the proper style:
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(style={'input_type': 'password'})
But Swagger displays the password input in the POST documentation as plain text (input type='text'). I'm not sure what is going wrong here, shouldn't django-rest-swagger be able to interpret the style here and render the input accordingly? Am I missing something? Any advice is appreciated, thanks much!
I'm still using DRF 2.4.4, but this is what I have to * out the field:
from django.forms.widgets import PasswordInput
class UserSerializer(serializers.HyperlinkedModelSerializer):
password = serializers.CharField(
required=False,
write_only=True, # <- never send password (or hash) to the client
allow_none=True, # <- allow users to not specify a password
blank=True,
# this creates a password input field for DRF-only
# front end (not sure about swagger)
# vvvvvvvvvvvvvvvvvv
widget=PasswordInput
)
class Meta:
model = User
fields = ('url', 'id', 'username', 'password', [...]

Django registration and multiple profiles

I'm using django-registration in my application. I want to create different kinds of users with different profiles.
For example, one user is a teacher and another user is a student.
How can I modify registration to set the user_type and create the right profile?
Long answer :p
I've found The Missing Manual post invaluable for this kind of problem as it explains many of features of the django-profiles and django-registration systems.
I'd suggest using multi table inheritance on the single profile you're allowed to set via the AUTH_PROFILE_MODULE
For instance
#models.py
class Profile(models.Model):
#add any common fields here (first_name, last_name and email come from User)
#perhaps add is_student or is_teacher properites here
#property
def is_student(self):
try:
self.student
return True
except Student.DoesNotExist:
return False
class Teacher(Profile):
#teacher fields
class Student(Profile):
#student fields
django-registration uses signals to notify you of a registration. You should be creating the profile at that point so you are confident that calls to user.get_profile() will always return a profile.
The signal code used is
#registration.signals.py
user_registered = Signal(providing_args=["user", "request"])
Which means when handling that signal you have access to the request made. So when you POST the registration form include a field that identifies what type of user to create.
#signals.py (in your project)
user_registered.connect(create_profile)
def create_profile(sender, instance, request, **kwargs):
from myapp.models import Profile, Teacher, Student
try:
user_type = request.POST['usertype'].lower()
if user_type == "teacher": #user .lower for case insensitive comparison
Teacher(user = instance).save()
elif user_type == "student":
Student(user = instance).save()
else:
Profile(user = instance).save() #Default create - might want to raise error instead
except KeyError:
Profile(user = instance).save() #Default create just a profile
If you want to add anything to the model that is created, that isn't covered by default field values, at registration time you can obviously pull that from the request.
http://docs.djangoproject.com/en/1.2/topics/auth/#groups
Django groups are a great way to define what you are looking for.
You can have one User extended profile that will contain all attributes of teachers and students.
class MasterProfile(models.Model):
user = models.ForeignKey(User, unique=True)
# add all the fields here
Then define groups: teacher and student and you associate each MasterProfile to either a teacher or a student.
Django Group table can help you define your various roles and allocate users to groups correctly.
I had the same issue and I tried the answer suggested by Chris, however it didn't work for me.
I'm only a newbie in Django, but I think the args taken by handler create_profile should match those under providing_args by signal user_registered, and in Chris's answer they don't (I think they probably match those passed by signal post_save, which I've seen used in the Missing Manual that he quotes)
I modified his code to make args match:
def create_profile(sender, **kwargs):
"""When user is registered also create a matching profile."""
request, instance = kwargs['request'], kwargs['user']
# parse request form to see whether profile is student or teacher
try:
user_type = request.POST['usertype'].lower()
print(user_type)
if user_type == "teacher": #user .lower for case insensitive comparison
Teacher(user = instance).save()
elif user_type == "student":
Student(user = instance).save()
else:
Userprofile(user = instance).save() #Default create - might want to raise error instead
except KeyError:
Userprofile(user = instance).save() #Default create just a profile
and seems to be working now