I am using django-registration app. and have following code in forms.py
from django.contrib.auth.forms import UserCreationForm
from registration.forms import RegistrationFormUniqueEmail
from django import forms
from django.contrib.auth.models import User
from accounts.models import UserProfile
from pprint import pprint
class UserRegistrationForm(RegistrationFormUniqueEmail):
#email = forms.EmailField(label = "Email")
fullname = forms.CharField(label = "Full name")
class Meta:
model = User
fields = ("fullname", "email", )
def __init__(self, *args, **kwargs):
super(UserRegistrationForm, self).__init__(*args, **kwargs)
del self.fields['username']
def save(self, commit=True):
user = super(UserRegistrationForm, self).save(commit=False)
user.userprofile.full_name = self.cleaned_data["fullname"]
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
I inherited from Django-registraion app's class RegistrationFormUniqueEmail so I when called save method at user = super(UserRegistrationForm, self).save(commit=False) it says, that save attribute doesnot exist. I actually wrote this code with inheritance from UserCreationForm .
I have just read the comment for SuperClass of RegistrationFormUniqueEmail that is :
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
These comments ask to not define another save method but I need to. So is there way that I can do define save method and call parent save method too to define additional fields? Following is the code of django-registration apps's forms.py:
"""
Forms and validation code for user registration.
"""
from django.contrib.auth.models import User
from django import forms
from django.utils.translation import ugettext_lazy as _
# I put this on all required fields, because it's easier to pick up
# on them with CSS or JavaScript if they have a class of "required"
# in the HTML. Your mileage may vary. If/when Django ticket #3515
# lands in trunk, this will no longer be necessary.
attrs_dict = {'class': 'required'}
class RegistrationForm(forms.Form):
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
label=_("Username"),
error_messages={'invalid': _("This value may contain only letters, numbers and #/./+/-/_ characters.")})
email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("E-mail"))
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Password"))
password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Password (again)"))
def clean_username(self):
"""
Validate that the username is alphanumeric and is not already
in use.
"""
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError(_("A user with that username already exists."))
else:
return self.cleaned_data['username']
def clean(self):
"""
Verifiy that the values entered into the two password fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
class RegistrationFormTermsOfService(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which adds a required checkbox
for agreeing to a site's Terms of Service.
"""
tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
label=_(u'I have read and agree to the Terms of Service'),
error_messages={'required': _("You must agree to the terms to register")})
class RegistrationFormUniqueEmail(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which enforces uniqueness of
email addresses.
"""
def clean_email(self):
"""
Validate that the supplied email address is unique for the
site.
"""
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
return self.cleaned_data['email']
class RegistrationFormNoFreeEmail(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which disallows registration with
email addresses from popular free webmail services; moderately
useful for preventing automated spam registrations.
To change the list of banned domains, subclass this form and
override the attribute ``bad_domains``.
"""
bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com',
'googlemail.com', 'hotmail.com', 'hushmail.com',
'msn.com', 'mail.ru', 'mailinator.com', 'live.com',
'yahoo.com']
def clean_email(self):
"""
Check the supplied email address against a list of known free
webmail domains.
"""
email_domain = self.cleaned_data['email'].split('#')[1]
if email_domain in self.bad_domains:
raise forms.ValidationError(_("Registration using free email addresses is prohibited. Please supply a different email address."))
return self.cleaned_data['email']
I just want to know that how can I override save() method or else how can I create new additional fields?
I have found solution of my own posted problem:
I have removed that django-registration app's RegistrationFormUniqueEmail, and instead I am inheriting from UserCreationForm and added the required method into my own UserRegistrationForm so I am able to override save method and able to do the things that I wanted to .
Related
There are two different types of users, A & B. Users of type A authenticate using username and password while type B have email and password. The project uses token based authentication.
The question is: how to manage both user types simultaneously?
The approaches I have considered:
Separate models for both user types and implement a custom auth backend to authenticate user signing in using email
Single user model to cater for both user types. However, this would mean I have to an add additional field (email) and populate some random value for users of type A
Apart from this, I have to store some custom information for logged in users like device type, location etc. Is it possible to override authtoken_token table to store this info or I have to create a separate model for that?
This following implementation should work (untested):
from rest_framework import serializers as rfs
from rest_framework.decorators import api_view
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from django.contrib.auth import get_user_model
User = get_user_model()
### serializers.py
class UserSerializer(rfs.ModelSerializer):
class Meta:
model = User
fields = ["username", "email"]
class UserLoginSerializer(rfs.Serializer):
username = rfs.CharField(required=False)
email = rfs.EmailField(required=False)
password = rfs.CharField(style={"input_type": "password")
user = UserSerializer(read_only=True)
def validate(self, data):
username = data.get("username", None)
email = data.get("email", None)
# case: both were given
if username and email:
raise rfs.ValidationError("Only one of email or username should be given.")
# case none were given
elif (not username and not email):
raise rfs.ValidationError("One of email or username is required.")
elif username:
data["user"] = User.objects.get(username=username)
elif email:
data["user"] = User.objects.get(email=email)
return data
### views.py
#api_view(["POST"])
def login_view(request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
"token": token.key,
"user_id": user.pk,
})
Explanation:
Basically, we have created a serializer that checks that only of either username or email is POSTed by the user.
For your 2nd question,
I have to store some custom information for logged in users like device type, location etc.
you can probably create a model such as:
from django.db import models
from rest_framework.authtoken.models import Token
class UserTokenClient(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="token_client")
token = models.ForeignKey(Token, related_name="client")
## extra fields you want
device_type = models.CharField()
or, you can use the package I wrote django-rest-durin which tries to solve this problem by associating a Client model between each User and Token.
You can easily subclass the Client model from the models.py to add your own custom fields, like so:
from durin.models import Client
class CustomClient(Client):
## extra fields you want
device_type = models.CharField()
and then override the LoginView similar to how it is done in this project's code.
I have a feature on my website where a user can share content with another user registered on the site. They do this by entering in an email belonging to another user. This is then posted, setting the desired user to as a shared owner of content in the model.
What is the best way to check that the email address belongs to a registered user of the site?
Thanks!
I think the efficient way is to search for the user with the given mail. Django User already has a mail field that is unique.
if you want to write from basic:
from django.core.validators import validate_email
class SampleForm(forms.Form):
mail = forms.CharField(max_length=50)
def clean(self):
cleaned_data = super(SampleForm, self).clean()
mail = cleaned_data.get('mail')
# validate the structure of the mail address
try:
validate_email(mail)
except validate_email.ValidationError:
raise forms.ValidationError('email is not valid')
# now find if mail has registered
try:
User.objects.get(email=mail)
except User.DoesNotExist:
raise forms.ValidationError('This mail address is not registered')
return cleaned_data
I have extended my UserManager with a new method create_inactive_user. But how do I use UserCreationForm?
class UserManager(UserManager):
def create_inactive_user(self, username, email, password):
user = self.create_user(username, email, password)
user.is_active = False
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
activation_key = hashlib.sha1(salt+user.username).hexdigest()
user.activation_key = activation_key
user.save()
return user
I can see in https://github.com/django/django/blob/master/django/contrib/auth/forms.py that UserCreationForm is a ModelForm which saves the object, so how can I be sure to sign up the users though create_inactive_user() in my FormView?
Is it something like this:
class SignupView(FormView):
form_class = UserCreationForm
template_name = 'signup.html'
def form_valid(self, form):
User.objects.create_inative_user(form.cleaned_data['username'], form.cleaned_data['email'], form.cleaned_data['password'])
return super(SignupView, self).form_valid(form)
Looks like django-registration does exactly what you're trying to do, with all views and forms included. Looks like their approach is to use a generic form, not a model one. From the quickstart doc:
A user signs up for an account by supplying a username, email address and password.
From this information, a new User object is created, with its is_active field set to False. Additionally, an activation key is
generated and stored, and an email is sent to the user containing a
link to click to activate the account.
Upon clicking the activation link, the new account is made active (the is_active field is set to True); after this, the user can log in.
I've created a User model for my django app
class User(Model):
"""
The Authentication model. This contains the user type. Both Customer and
Business models refer back to this model.
"""
email = EmailField(unique=True)
name = CharField(max_length=50)
passwd = CharField(max_length=76)
user_type = CharField(max_length=10, choices=USER_TYPES)
created_on = DateTimeField(auto_now_add=True)
last_login = DateTimeField(auto_now=True)
def __unicode__(self):
return self.email
def save(self, *args, **kw):
# If this is a new account then encrypt the password.
# Lets not re-encrypt it everytime we save.
if not self.created_on:
self.passwd = sha256_crypt.encrypt(self.passwd)
super(User, self).save(*args, **kw)
I've also created an authentication middleware to use this model.
from accounts.models import User
from passlib.hash import sha256_crypt
class WaitformeAuthBackend(object):
"""
Authentication backend fo waitforme
"""
def authenticate(self, email=None, password=None):
print 'authenticating : ', email
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
user = None
if user and sha256_crypt.verify(password, user.passwd):
return user
else:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I have ammended the settings.py file correctly and if I add some print statements to this backend I can see the user details print out. I don't recall reading that I need to implement is_authenticated in the django docs. Am I missing something silly?
I'm not quite sure why you have created a new User model instead of using Django's built-in one and adding a linked UserProfile, which is the recommended thing to do (until 1.5 is released, when pluggable user models will be available). However, yes you need to define an is_authenticated method, which always returns True: this is exactly what the built-in model does. The reason is that if you have an actual User, it will always be authenticated: otherwise, you will have an AnonymousUser object, whose is_authenticated method always returns False.
you dont have to reinvent the wheel. Just use Djangos build in authentication system and save yourself a lot of trouble. You can also extend it to your needs or use different authentication backends. Have a read here. HTH.
I have run into the following error trying to create a user in django:
>>> email = 'verylongemail#verylongemail.com'
>>> user_object = User.objects.create_user(username=email, email=email, password='password')
Data truncated for column 'username' at row 1
It seems Django has a limit on the number of chars allowed in a username. How would I get around this?
I've had to modify the auth_user table by hand to make the field longer and then convert emails into a username by removing the # symbol and the period (maybe other characters too, it's really not a great solution). Then, you have to write a custom auth backend that authenticates a user based on their email, not the username, since you just need to store the username to appease django.
In other words, don't use the username field for auth anymore, use the email field and just store the username as a version of the email to make Django happy.
Their official response on this topic is that many sites prefer usernames for auth. It really depends if you are making a social site or just a private site for users.
If you override the form for Django users you can actually pull this off pretty gracefully.
class CustomUserCreationForm(UserCreationForm):
"""
The form that handles our custom user creation
Currently this is only used by the admin, but it
would make sense to allow users to register on their own later
"""
email = forms.EmailField(required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
class Meta:
model = User
fields = ('first_name','last_name','email')
and then in your backends.py you could put
class EmailAsUsernameBackend(ModelBackend):
"""
Try to log the user in treating given username as email.
We do not want superusers here as well
"""
def authenticate(self, username, password):
try:
user = User.objects.get(email=username)
if user.check_password(password):
if user.is_superuser():
pass
else: return user
except User.DoesNotExist: return None
then in your admin.py you could override with
class UserCreationForm(CustomUserCreationForm):
"""
This overrides django's requirements on creating a user
We only need email, first_name, last_name
We're going to email the password
"""
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
# let's require these fields
self.fields['email'].required = True
self.fields['first_name'].required = True
self.fields['last_name'].required = True
# let's not require these since we're going to send a reset email to start their account
self.fields['username'].required = False
self.fields['password1'].required = False
self.fields['password2'].required = False
Mine has a few other modifications, but this should get you on the right track.
You have to modify the username length field so that syncdb will create the proper length varchar and you also have to modify the AuthenticationForm to allow greater values as well or else your users won't be able to log in.
from django.contrib.auth.forms import AuthenticationForm
AuthenticationForm.base_fields['username'].max_length = 150
AuthenticationForm.base_fields['username'].widget.attrs['maxlength'] = 150
AuthenticationForm.base_fields['username'].validators[0].limit_value = 150