User Authenication and Login ============================ * The aims of this tutorial is to get you familiar with the User Authentications mechanisms provided by Django. Django provides a number of helpful constructs when dealing with users. The first of these is Django's User model/table. This model holds pretty standard user fields: username, last_name, first_name, email, password, is_staff, is_active, is_superuser, last_login, date_joined. See https://docs.djangoproject.com/en/dev/topics/auth/ for more details about the User model. To extend the User model, you will need to add a UserProfile model/table, which has a one-to-one relationship with Django's User model/table. In the UserProfile table, you can store additional details about the user beyond what is provided by the User model. Note: make sure that 'django.contrib.auth' and 'django.contrib.contenttypes' are listed in your INSTALLED_APPS setting. You will also need to make sure that you have password hasher functions set up and included in your *settings.py* file: :: PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.BCryptPasswordHasher', 'django.contrib.auth.hashers.SHA1PasswordHasher', 'django.contrib.auth.hashers.MD5PasswordHasher', 'django.contrib.auth.hashers.CryptPasswordHasher', ) Adding in the password hashers is really important. If this is not included then passwords will not be encrypted properly, and will lead to errors when users try to log in. Creating the User Models/Tables ------------------------------- In Models.py, add in the following lines of code: :: from django.contrib.auth.models import User class UserProfile(models.Model): # This field is required. user = models.OneToOneField(User) # These fields are optional website = models.URLField(blank=True) picture = models.ImageField(upload_to='imgs', blank=True) def __unicode__(self): return self.user.username In the UserProfile class, a one to one field is added to link the User and UserProfile. To add some details about the user, we've added a field to store their website URL and an image of the user. Both of these fields can be left empty (i.e. blank), and for the ImageField we need to set an upload_to path (where images uploaded will be saved to.. the path is relative to the MEDIA_ROOT path set in the settings.py). Make sure the MEDIA_ROOT/imgs directory exists. In rango/admin.py import UserProfile and register the UserProfile model with admin, i.e. admin.site.register(UserProfile) Now update the database: :: python manage.py syncdb Creating a User Registration View/Template .......................................... While off the shelf User Registrations packages for Django exist, such as the Django Registration app: http://code.google.com/p/django-registration/, it is good to familarize yourself with the process of handling user registrations. To set up the views and templates required you will need to do the following: * Create a UserForm and UserProfileForm * Create a view to handle the creation of a user * Create a template that displays the UserForm and UserProfileForm. * Add in a url mapping to the view * Add a link in index.html to let users register. In *models.py* add: :: from django.forms import ModelForm class UserForm(forms.ModelForm): class Meta: model = User fields = ["username", "email", "password"] class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile In *views.py* add: :: from django.contrib.auth.models import User from rango.models import UserProfile from rango.models import UserForm, UserProfileForm from django.http import HttpResponse from django.shortcuts import render_to_response def register(request): context = RequestContext(request) registered = False if request.method == 'POST': uform = UserForm(data = request.POST) pform = UserProfileForm(data = request.POST) if uform.is_valid() and pform.is_valid(): user = uform.save() profile = pform.save(commit = False) profile.user = user profile.save() registered = True else: print uform.errors, pform.errors else: uform = UserForm() pform = UserProfileForm() return render_to_response('rango/register.html', {'uform': uform, 'pform': pform, 'registered': registered }, context) The body of the template, *register.html*: :: {% if registered %} Rango says: thank you for registering. {% else %} rango says: register here!
{% csrf_token %} {{ uform.as_p }} {{ pform.as_p }}
{% endif %} Note a couple of things here: ModelForms from Django provides a way to produce customized forms, with different widgets, labels, etc. These forms can be rendered as html in the template saving you lots of time messing about with html form code. Also, since the ModelForms are linked to Models, then they can save the data or obtain the data from the Model and save/display it. You'll notice that the html forms created include a "user" field. This is because the ModelForm created for UserProfile did not specify which fields to include within the html form, so it created all of them (i.e. user, website, picture). Specify which fields should be shown now, by updating the meta class within the UserProfileForm: :: fields = ['website','picture'] This will now only include the website and picture fields on the user profile form. Note that in the *register* view function, once a user object has been created, it can then be assigned to the UserProfile with *profile.user = user*. This performs the necessary one to one mapping between the User and UserProfile model. The ModelForms also provide details of any errors relating to the data entry, and whether the data meets all the Model conditions. In the previous section, you saw that the ModelForms can be customized to clean the data being pass through to handle such errors. In the register view, the code handles a GET request by showing empty User and the UserProfile forms. The code also handles the POST request which is triggered when the user presses the submit button on the registration form. If there are errors with the data, then on POST, these will be detected, and the forms will be shown again, with data in them, along with error messages. Error messages are also printed to the console. In the template, an *if* statement is used to display the form if the user has not registered, otherwise to display the form. This is a handy way to include or exclude pieces of functionality. Now that you have created the forms, view and template for registering, add the following urlpattern in rango/urls.py: :: url(r'^register/$', views.register, name='register'), And in the template *index.html*, add a link to register:: Register Here Try out this extra functionality and register a user, then check it out in the admin interface. Hopefully, all the data will have been persisted to your the database, so now you'll need to create the login functionality for your application. Adding User Login Functionality ------------------------------- Create a *login* view, by adding the following code to rango/views.py: :: from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect def user_login(request): context = RequestContext(request) if request.method == 'POST': username = request.POST['username'] password = request.POST['password'] user = authenticate(username=username, password=password) if user is not None: if user.is_active: login(request, user) # Redirect to index page. return HttpResponseRedirect("rango/") else: # Return a 'disabled account' error message return HttpResponse("You're account is disabled.") else: # Return an 'invalid login' error message. print "invalid login details " + username + " " + password return render_to_response('login.html', {}, context) else: # the login is a GET request, so just show the user the login form. return render_to_response('login.html', {}, context) Basically, the *user_login* view, tests to see if a POST request has been made, if so, it extracts the username and password from the POST, and then tries to authenticate the user, using Django's *authenticate* function the user model. If a User is returned, and is active then, using Django's *login* function, the login event is handled (i.e. the user login is recorded, cookies are generated and sent to the client, which can be extract from subsequent requests.) Django also has a *logout* function which will securely log the user out (i.e. expire cookies, etc). Next Django's *HttpResponseRedirect* function is called to redirect the user to the login in page. This is a pretty useful, which lets you use pre-existing views. The rest of the login method handles the other cases (invalid login, disabled account or logging in) Create a *login.html* template that will capture the username and password of the user: ::
Rango
{% csrf_token %} Username:
Password:
Make sure you add in the url mapping for the login view with: :: rl(r'^login/$', views.user_login, name='login'), Now, in *index.html*, add in register and login links: ::

Register | Login

Restricting Access .................. To restrict access to views, there are two ways: * directly examining the request, or * using a convenience decorator function. The direct way checks whether the *request.user.is_authenticated()* as follows: :: def some_view(request): if not request.user.is_authenticated(): return HttpResponseRedirect('/rango/login/') else: ... Otherwise, you can use the decorator function *@login_required*. By adding the @login_required decorator function, before your view is executed Django will check and see if an authenticated user is currently logged in. If the user is logged in then they will be able to view the page, otherwise they will notified that they do not have access to that particular page. To try out this functionality, create a view called, *restricted* in views.py and decorate it as follows: :: from django.contrib.auth.decorators import login_required @login_required def restricted(request): return HttpResponse('Rango says: since you are an authenticated user you can view this restricted page.') In *rango/urls.py*, map a url to this view, by adding the following code to urlpatterns: :: url(r'restricted', views.restricted, name='restricted' ) To handle the case when a user has not logged in, you will need to set, *LOGIN_URL* in settings.py: :: LOGIN_URL = '/rango/login' The *@login_required* decorator will then redirect users not logged in to this url. Logging out ----------- To ensure that users can log out securely use Django's *logout* function. :: @login_required def user_logout(request): context = RequestContext(request) logout(request) # Redirect back to index page. return HttpResponseRedirect('/rango/') Now add in the url mapping: *url(r'^logout/$', views.user_logout, name='logout'),* and update the *index.html* template as follows: ::

{% if user.is_authenticated %} Welcome {{ user.username }} | Restricted Page | Logout {% else %} Register | Login {% endif %}

Recall that in the *index* view the context is passed through to the template, part of this context includes a reference to the the user model. This means that you are able to access the data associated with the current user in the template, by referring to the user object. Here, the index template tests to see if the user is authenticated, and if they are displays their username, and provides a link to the restricted content: otherwise register and login options are presented. In this part of the tutorial, only some of the User Authentication functions have been used to demonstrate how to register a user and how to log users in and out. However, it is worth learning more about User Authentication in Django at: https://docs.djangoproject.com/en/1.4/topics/auth/ as Django provides many other helpful functions and helper templates and views to handle common aspects of user authentication and registration. Exercises --------- * Customize the application so that only registered users can add/edit, while non-registered can only view/use the categories/pages * Handle lost passwords by adding a form that emails the password to the user