How to subclass registration form in django-registration - django

I wish to have the user agree to a TOS and to also support unique emails. django-registration has two different subclassed registration forms that does this: RegistrationFormTermsOfService and RegistrationFormUniqueEmail.
Do I have to make my own sublcass of RegistrationForm and then provide both those features? If so, how would this be accomplished? Would the registration form live inside my app's forms.py or somewhere else?

A quick look at the source for the two classes shows:
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,
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']
As you can see these two classes don't overwrite methods defined by the other so you should be able to just define your own class as being:
from registration.forms import RegistrationFormUniqueEmail, RegistrationFormTermsOfService
class RegistrationFormTOSAndEmail(RegistrationFormUniqueEmail, RegistrationFormTermsOfService):
pass
And it should function, however I have not tested this. As to where to place this class; forms.py is a good location.
Update:
A little reading at https://django-registration.readthedocs.org/en/latest/views.html which tells us that we can pass the view some parameters via the url definition; for instance a form class.
Simply use a URL like:
url(r'^register/$',
RegistrationView.as_view(form_class=RegistrationFormTOSAndEmail),
name='registration_register')

Related

How does Flask-WTF use methods defined in a form?

When creating a form using Flask-WTF and implementing a method, say, to check whether a username is available, I'm curious to how/why such methods work.
What I mean is, not once do you reference any of methods defined in the form class throughout the rest of your code; they seem to work on their own without any interference - can somebody explains how and why it works this way?
For example:
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
...
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
The above code seems to run validate_username on submission of the form without myself having to call it.
This is by convention - when your form is being validated, it will check whether you have defined any custom inline validators in the form of validate_your_field and run these.
You can find more details in the Custom Validators section of documentation.

How do I specify (1) an order and (2) a meaningful string representation for users in my Django application?

I have a Django application with users. I have a model called "Course" with a foreign key called "teacher" to the default User model that Django provides:
class Course(models.Model):
...
teacher = models.ForeignKey(User, related_name='courses_taught')
When I create a model form to edit information for individual courses, the possible users for the teacher field appear in this long select menu of user names.
These users are ordered by ID, which is of meager use to me. How can I
order these users by their last names?
change the string representation of the User class to be "Firstname Lastname (username)" instead of "username"?
Firsty, the order. You can define a default order of model using the nested Meta class. Check the ordering section.
Secondly, representation. You have to define a __str__()/__unicode__() methods for your model. They should return a string which represents an object. You can see an example in documentation. BUT, since User is a model from an outer module it may be hard to do it in that way.
You probably can:
monkey-patch the User model meta class during app initialization OR
subclass the User model, add Meta to the subclass and use it in place of the User OR
write a custom field / form template which uses objects attributes instead of calling str()
it all depends on the current case
Abstract
If you're only dealing with the admin, it's better not to tinker with the User model itself, as subclassing User can be a pain down the road (especially when / if other developpers are going to work on your project), but use the admin's customization options.
Solution
To solve your issue in the admin, you could use the ModelAdmin option raw_id_fields.
This will replace the <select> input with a widget that you can click to be redirected to your User admin and choose your user from.
From there, it's trivial to customize your User admin so that it:
Displays your users in a relevant order (ModelAdmin.ordering)
Displays the fields you're interest in (ModelAdmin.list_display)
Just remember than when you're registering your User admin, you need to use:
admin.site.unregister(User) #This!
admin.site.register(User, MyCustomUserAdmin)
Alternate solution
Alternatively, you can always use ModelAdmin.formfield_overrides, which is more powerful but more complicated.
Reference
You should look into the Django documentation for details on how to use those attributes.
You could achieve this by customizing form field, here the forms.ModelChoiceField
class CustomizedModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
# change default ordering
super(CustomizedModelChoiceField, self).__init__(*args, **kwargs)
self.queryset = self.queryset.order_by('last_name')
def label_from_instance(self, obj):
# change representation per item
return u'{obj.first_name} {obj.last_name} ({obj.username})'.format(obj=obj)
Then use it in your form to replace default ModelChoiceField.
In Django Admin, it looks like
class CourseAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'teacher':
kwargs['form_class'] = CustomizedModelChoiceField
return super(CourseAdmin, self).formfield_for_dbfield(db_field, **kwargs)

does django models offer something similar to forms' clean_<fieldname>()?

I am trying to move all business-logic-related validations to models, instead of leaving them in forms. But here I have a tricky situation, for which I like to consult with the SO community.
In my SignupForm (a model form), I have the following field-specific validation to make sure the input email does not exist already.
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
If I were to move this validation to the models, according to the official doc, I would put it in clean() of the corresponding model, ExtendedUser. But the doc also mentions the following:
Any ValidationError exceptions raised by Model.clean() will be stored
in a special key error dictionary key, NON_FIELD_ERRORS, that is used
for errors that are tied to the entire model instead of to a specific
field
That means, with clean(), I cannot associate the errors raised from it with specific fields. I was wondering if models offer something similar to forms' clean_<fieldname>(). If not, where would you put this validation logic and why?
You could convert your clean method into a validator and include it when you declare the field.
Another option is to subclass the model field and override its clean method.
However there is no direct equivalent of defining clean_<field name> methods as you can do for forms. You can't even assign errors to individual fields, as you can do for forms
As stated in the comment I believe you should handle this validation at the modelform level. If you still feel like it would be better to do it closer to the model, and since they can't be changed, I would advise a change directly at the db level:
ALTER TABLE auth_user ADD UNIQUE (email)
Which is the poor way to add the unique=True constraint to the User model without monkey patching auth.
As requested, I think that a good way to go about customizing different forms should be done by inheriting from a base modelform. A good example of this is found in django-registration. The only difference is that instead of the parent form inheriting from forms.Form you would make it a modelForm:
class MyBaseModelForm(ModelForm):
class Meta:
model = MyModel
You could then inherit from it and make different forms from this base model:
class OtherFormWithCustomClean(MyBaseModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email

django registration - allows multiple users for an email id

I am trying out django-registration. I found that it allows multiple registration for same emailid. I want to prevent that. What is the best way to do that?
ok, I see there is a subclass RegistrationFormUniqueEmail. Now, how to use this class? I changed this
def get_form_class(self, request):
return RegistrationFormUniqueEmail
But, it must be better to change this from my application rather than in source code. So, how do I do that?
thanks
Once you've added registration to your settings file, you can use the form in your views.py like so:
from registration.forms import RegistrationFormUniqueEmail
form = RegistrationFormUniqueEmail()
That's it. That will give you the form that you need and will take care of the unique email validation.

Cleanest way to implement multiple email system in django?

Hey everyone, I am pretty sure this is a fairly common problem.
So in order to register an account my site you need an email address from certain school domain (like facebook). This wouldn't be that big a problem until you start integrating other apps, like django-notification and django-registration and django-socialregistration into your site where they are sending email via user.email.
I have asked my users and most of them want an 'active_email' option - that means that they can change the email to their designated gmail or whatever.
I have come up with the following solution which isn't the cleanest of all:
First, I inherit from User in django.contrib.auth and call this new class MultipleEmailUser, with email=active_email and official_email=sch_email.
Then I override django.contrib.auth's UserManager to change the API slightly,
And the most painful part is to change all the source code that has User.object.find() to MultipleEmailUser.find().
Can someone suggest me a cleaner way? (My biggest headache arise from other apps only permitting to send email to User.email.)
You don't need - or want - to modify the User class. Just set up a UserProfile class with a OneToOneField back to User, and set the AUTH_PROFILE_MODULE setting. See the documentation.
Rather than a true datastore attribute, you could use a property called 'email' to expose access to whatever data you want.
Basic property syntax (from the python docs):
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
In your case, you could create true datastore attributes named, for instance, auth_email and active_email. Then, the User.email property could perform some logic in its getter function, to determine which one to return (i.e. if active_email is set, return it; otherwise, return auth_email)
It's worth noting that the syntax for property has undergone some flux. As of python 2.7, it can be implemented in a more readable way, as a decorator:
class User(BaseModel):
#property # email
def email(self):
if self.active_email:
return self.active_email
return self.auth_email