Wednesday 19 March 2014

Customizing django-registration


Whatz up?!

The integration of django-register is awesome! But if you want to customize it with additional models it can be confusing.

Here in example I will show how to create your custom backend, form to handle it (custom validator and model choice field).

Note, the django I use is version v1.5.4 django-registration v1.0. Some line breaks are imposed due to layout of this page.

Thus, bellow you will find everything you need to:
  • Create custom backend
  • Add custom form 
  • Create custom validation
  • Badabung! :D

Below is and example extending
from registration.backends.default.views.RegistrationView
First of all, inherit RegistrationView, then override register() and get_form_class() functions. In the examples to follow it is assumed that the class is saved to regbackend.py.
#regbackend.py
from registration.backends.default.views import RegistrationView

#create custom registration backend
class StudentBackend(RegistrationView): 
    '''
    we just want to override register and fix profile creation
    ''' 
    def register(self,request, **kwargs):
        from usermanager.models import Profile
        user=super(StudentBackend,self).register(request, **kwargs)
        user.first_name = kwargs['first_name']
        user.last_name = kwargs['last_name']
        user.save()
        profile = Profile.objects.create(
                                user=user, course=kwargs['course'])
        profile.save()
        
    def get_form_class(self, request):
        """
        Return the default form class used for user registration.

        """
        from usermanager.form import UserRegistrationForm
        return UserRegistrationForm    
Below is the code from urls.py. Nothing fancy, just import your registration backend, and form, and redirect accounts/register/ to your backend.
#urls.py
from regbackend import StudentBackend
from form import UserRegistrationForm

urlpatterns = patterns('',
    url(r'^accounts/register/$',
        StudentBackend.as_view(form_class = UserRegistrationForm),
        name='registration_register'),
    (r'^accounts/', include('registration.backends.default.urls')),
    #more url configs
)
Following code is from form.py:
#form.py
from django import forms
from django.contrib.auth.models import User
from usermanager.models import Course, Profile
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _



attrs_dict = { 'class': 'required' }

#this function checks for the allowed domain:
def affiliation_valid_email(value):
    allowed_domain=['domain.se','domain.se']
    #not the prettiest soliution 
    v_error=_("Email does not match allowed domains (%s, %s)"
                           %(allowed_domain[0],allowed_domain[1])
    if value.split('@')[1] not in allowed_domain:
        raise forms.ValidationError(v_error)
    else:
        return value


#the registration form is inherited from django-registration   
class UserRegistrationForm(RegistrationForm):  
    
    class Meta:
        model = User
        fields = ("first_name",
                  "last_name",
                  "username", 
                  "email", 
                  "password1", 
                  "password2")
    #username is enforced to some particular rules 
    u_error = _("User name needs to match domain convention.")   
    username = forms.RegexField(regex=r'^\w',
                                max_length=9,
                                widget=forms.TextInput(attrs=attrs_dict),
                                label=_("Username - same as in domain!"),
                                error_messages={ 
                                'invalid': u_error })
    first_name = forms.CharField(max_length=30)
    last_name = forms.CharField(max_length=30)
    course = forms.ModelChoiceField(queryset=Course.objects,required=True)
    email = forms.EmailField(validators=[affiliation_valid_email],
                             widget=forms.TextInput(
                                     attrs=dict(attrs_dict,maxlength=75)
                                     ),
                                     label=_("domain - Email address"))
    

    
    def clean(self):
        #clean with super
        ue_error = _("Email and user name do not match.")
        super(UserRegistrationForm, self).clean()
        #this is just checking that username is the same as in email
        if self.cleaned_data['email'].split('@')[0] == 
                                        self.cleaned_data.get("username"):
            return self.cleaned_data
        else:
            raise forms.ValidationError(ue_error)
         
    def save(self, commit=True):
        user = super(UserRegistrationForm, self).save(commit=True)    
        user.email = self.cleaned_data["email"]
        user.first_name =  self.cleaned_data["first_name"]
        user.last_name =self.cleaned_data["last_name"]
        profile = Profile(
                   systemuser=user, course = self.cleaned_data["course"])

    if self.commit:
        user.save()
        profile.save()
    return user

Models used in this example:
#models.py

class Course(models.Model):
    code = models.CharField(max_length=6)
    teacher = models.CharField(max_length=30)
    
    class Meta:
        app_label = "usermanager"
        verbose_name = "Course"
        verbose_name_plural = "Courses"
    
    def __unicode__(self):
        return "%s" % self.code
    
class Profile(models.Model):
    user=models.ForeignKey(User, unique=True)
    course=models.ForeignKey('usermanager.Course')
    created = models.DateTimeField(auto_now_add=True)
    updated_local = models.DateTimeField(blank=True, null=True)
    

    def getCourse(self):
        return "%s" %self.course
    
    def save(self,*args,**kwargs):
        self.updated_local = datetime.now()
        super(Profile, self).save(*args,**kwargs)
    
    def __unicode__(self):
        return "Profile: %s" %self.user

Finally, remember to enable registration in installed apps before running
python manage.py syncdb
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'django.contrib.admindocs',
    'registration',
)
That's it foks, happy coding! L