User¶
1. User Registration¶
Routing the URLS¶
Add the URL in urlpatterns
in app/urls.py
:
urlpatterns = [
url(r'^register/$', app_views.register, name='register'),
]
Creating a form¶
Create a file forms.py
in app
and import forms
from Django.
from django import forms
We are going to use the form UserCreationForm
provided by Django as our form1
, the form contains basic information we must know about our users. The form also needs a model called “User“:
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
We could simply use UserCreationForm
and we don‘t need to write the forms.py
file. But it only comes with username and password and we do want to know more about our users. Therefore, we are going to extend the UserCreationForm
by creating a form of our own:
class RegistrationForm(UserCreationForm):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
email = forms.EmailField(max_length=254)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
Here we use the User model from Django, it comes with several fields containing what we defined above(name and email) but not required by default. Since we want to know the name and email of our users, in our RegistrationForm
, we specify the 2 CharField and an EmailField as required(explained in the tips below). Once we are done with defining those fields, we need to add them into Meta
so the forms know which ones and in what order(from left to right) we want to put them in. (If we use a {% for %}
loop to display them through Django template, the website will show them in order.)
Tips:
In the form that we created, if we do not specify required=False
such as
email = forms.EmailField(max_length=254, required=False)
when we define a field, it will be a required information by default, and it will produce an error if users do not enter in an acceptable format(such as “12345“ in EmailField
).
If we want to put some help text next to it, we can do
email = forms.EmailField(max_length=254, help_text='Blah blah blah.')
and then add the following in the template:
{% if field.help_text %}
{{ field.help_text }}
{% endif %}
Creating a model¶
Good! We are done with the required field, now we will use a different approach to recording optional information. Go to app/models.py and create our own model, `RegisterUser‘ for optional information:
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class RegisterUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(verbose_name='Department(Optional)', max_length=100, blank=True, null=True)
organization = models.CharField(verbose_name='Organization(Optional)', max_length=100, blank=True, null=True)
title = models.CharField(verbose_name='Title(Optional)', max_length=100, blank=True, null=True)
country = models.CharField(verbose_name='Country(Optional)', max_length=50, blank=True, null=True)
about= models.TextField(verbose_name='About(Optional)', max_length=500, blank=True, null=True)
You may notice that we have 2 more parameters here: blank and null. They tell the database to accept empty or blank value. If you want more information, please click on the link to read Django Documentation.
We also used a OneToOneField to relate the User object to this model. If we want to use any information in this RegisterUser
model, for instance, the department, we will call: .registeruser.department
(all lowercase).
Now we go back to app/forms.py
to add our second form.
from .models import RegisterUser
class AdditionalForm(forms.ModelForm):
class Meta:
model = RegisterUser
exclude = ('user')
Because we will use exactly the RegisterUser model we just created, so we don‘t need to define any other fields. However, we do want to exclude the ‘user‘ object that relates the RegisterUser
model to the user, but we don‘t want to create a new user object with the same information in the database. Recall that this model is to store optional information, and all the crucial information is in our first form RegistrationForm
.
Writing a register view:¶
Now, we are going to create a view for or user registration. Go to app/views.py
and add the following:
def register(request):
if request.method == 'POST':
form1 = RegistrationForm(request.POST)
form2 = AdditionalForm(request.POST)
if form1.is_valid() and form2.is_valid():
model1 = form1.save()
model2 = form2.save(commit=False)
model2.user = model1
model2.save()
return redirect('index')
else:
form1 = RegistrationForm()
form2 = AdditionalForm()
return render(request, 'app/register.html', {'form1': form1, 'form2': form2})
The reason we have commit=False
in model2 = form2.save(commit=False)
is that we do not want form2
to create a new user, so when we create form2
, it does not sync to the database, it allows us to make changes before we sync them. After we assign the form1.user
to form2.user
, we call model2.save()
and the data of form1
and form2
goes under the same user in the database. After registration, we will redirect the user to our home page(redirect('index')
).
Templates:¶
We used 2 {% for %}
loops to display the required information and optional information as 2 columns:
{% for field in form1 %} <!-- or for field in form2 -->
<p>
{{ field.label_tag }}<br>
{{ field }}
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
</p>
{% endfor %}
2. User Login and Logout¶
We are using login and logout views written by Django, imported from django.contrib.auth.view.Login, this view does basic authentication such as verify username and password when users logging in.
To better distinguish the imported views and views written by ourselves, we imported auth.view
(views from Django) as “auth_view” and app.view
(Our views) as “app_view”.
from . import views as app_views
from django.contrib.auth import views as auth_views
Routing the URLS¶
Since all of the links related to login and register is in app “app”, so we will put our URLs in app.urls(If there isn‘t such a file, create one) and include them in our project URLs (inside the folder that has settings.py).
cam2webui/urls.py
from django.conf.urls import url,include
urlpatterns = [
url(r'^', include('app.urls')),
]
app/urls.py:
from . import views as app_views
from django.contrib.auth import views as auth_views
urlpatterns = [
url(r'^login/$', auth_views.login, name='login'),
url(r'^logout/$', auth_views.logout),
]
If we don’t specify the template for this login view, Django will look for template from registration/login.html
. But we want to keep our template together inside app
folder, so we specify the template in the url:
url(r'^login/$', auth_views.login, {'template_name': 'app/login.html'}, name='login'),
Besides, after users logged out, we want them to go back to home page, so we use next_page
parameter to redirect:
url(r'^logout/$', auth_views.logout, {'next_page': '/'}),
The Django login view implements a redirect function, all we need to do is to go to settings.py
and add:
LOGIN_REDIRECT_URL = 'index'
(We use the names we defined for our URLs. For here, index
is the home page.)
Login template¶
The website design will not be covered in this documentation. Apart from that, we simply use for loops to display all the field necessary for login and any error corresponding to each field:
{% block content %}
<form role="form" action="" method="post" class="login-form">
{% csrf_token %}
{% for field in form %}
<p>
{{ field.label_tag }}<br>
{{ field }}
</p>
{% endfor %}
<button type="submit" class="btn">Sign in</button>
{% if form.non_field_errors %}
<ul class='form-errors'>
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</form>
{% endblock %}
Instead of creating a successful login page after user logged in, it is easier for users to know whether they have logged in by showing the status on the top right corner of the website.
Therefore, we will use an {% if %}{% else %}
statement in our base.html
, which all other template extends, so the user can see the status on all pages of our website.
{% if user.is_authenticated %}
<li><a href="/profile/">{{request.user.username}}'s Profile </a></li>
<li><a href="/logout/">Logout</a></li>
{% else %}
<li><a href="/login/">Login</a></li>
<li><a href="/register/">Register</a></li>
{% endif %}
User.is.authenticated
returns True
if a user has logged in, then the website will display the name of the user with the links to their profile and logout. If it returns False
, then the website will show the links to login and register.
3. Forgot Password (Password Reset)¶
Goal¶
Allow users to reset their password through email.
The user will go to the password reset page and enter their email. When the email is inside our database, we send a confirmation with a link to reset the password.
approach¶
Django has its own password reset views. We just need to create templates and link the urls.
add the following to urls.py
:
url(r'^password_reset/$', auth_views.password_reset, name='password_reset'),
url(r'^password_reset_email_sent/$', auth_views.password_reset_done, name='password_reset_done'),
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
auth_views.password_reset_confirm, name='password_reset_confirm'),
url(r'^reset/done/$', auth_views.password_reset_complete, name='password_reset_complete'),
Create a new directory registration
under templates and put our templates inside.
password_reset_form.html
correspond to password_reset
: ask user to enter their email address
password_reset_done.html
correspond to password_reset_email_sent
: tells user the confirmation email has been sent
password_reset_subject.txt
: Subject of confirmation email
password_reset_email.html
: Content of confirmation email
password_reset_confirm.html
: ask user to set new password
password_reset_complete.html
: tells user the password has been reset
4. User Third party login¶
This page will walk you through some guides on writing third party login including google login and github login on the login page.
Installation¶
For the django third party login, there is a django app called social-auth-app-django already built for us. So we can simply download this library to make third party login easier to build. We can use python pip to install the app.
pip install social-auth-app-django
Then visit settings.py in our app and include that in the installed apps:
INSTALLED_APPS = [
'app.apps.AppConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'widget_tweaks',
'social_django', # <---- Add this one
]
After you save the settings.py. Go to terminal and migrate the database.
python manage.py migrate
Configuration¶
Back to the settings.py, we have several more things to modify.
The first one is Middleware, we need to add one more thing:
MIDDLEWARE = [
'app.middleware.basicauth.BasicAuthMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'social_django.middleware.SocialAuthExceptionMiddleware', # <--- Add this one
]
Then we need to update the template in setting.py:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends', # <--- Add this one
'social_django.context_processors.login_redirect', # <--- Add this one
],
},
},
]
Next we will add authentication backend in settings, since we need google and github login, we add github oauth and google oauth. If you would like to add something like facebook login, you can add ‘social_core.backends.facebook.FacebookOAuth2‘ in this model backend if you want.
AUTHENTICATION_BACKENDS = (
'social_core.backends.github.GithubOAuth2',
'social_core.backends.google.GoogleOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
Let’s set the default values for the LOGIN_URL, LOGOUT_URL and the LOGIN_REDIRECT_URL. The LOGIN_REDIRECT_URL will be used to redirect the user after authenticating from Django Login and Social Auth.
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'
LOGIN_REDIRECT_URL = 'home'
SOCIAL_AUTH_LOGIN_ERROR_URL = '/register/'
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/oauthinfo/'
SOCIAL_AUTH_RAISE_EXCEPTIONS = False
Update the urls.py add the social-auth-app-django urls:
url(r'^oauth/', include('social_django.urls', namespace='social')),
GitHub Authentication¶
Log in to your GitHub account, go to Settings. In the left menu you will see Developer settings. Click on OAuth applications.
In the OAuth applications screen click on Register a new application.
Fill in the form with any name, url or description. The only important one is Authorization callback URL:
Notice that I’m putting a localhost URL. http://localhost:8000/oauth/complete/github/. You must use this url to accomplish oauth login for github.
After you create the app, you can get:
- client id
- client secret
Copy and paste those items and send them into the environment. Don‘t make it public!!
Then in settings.py, add two varialbes called ‘SOCIAL_AUTH_GITHUB_KEY‘ and ‘SOCIAL_AUTH_GITHUB_SECRET‘
SOCIAL_AUTH_GITHUB_KEY = os.environ['GITHUB_KEY']
SOCIAL_AUTH_GITHUB_SECRET = os.environ['GITHUB_SECRET']
Settings for github login is finished!
Google Authentication¶
For google authentication, it is similar to github authentication. This time we need to acquire google credentials.
Visit https://console.developers.google.com/apis/credentials
Click on ‘create credentials‘ -> ‘oauth client id‘ -> ‘web application‘ -> Fill in the form
The same as before, the only important one is Authorized redirect URIs:
Notice that I’m putting a localhost URL. http://localhost:8000/oauth/complete/google-oauth2/. You must use this url to accomplish oauth login for google.
After you create the app, you can get:
- client id
- client secret
Copy and paste those items and send them into the environment. Don‘t make it public!!
Then in settings.py, add two varialbes called ‘SOCIAL_AUTH_GOOGLE_OAUTH2_KEY‘ and ‘SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET‘
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ['GOOGLE_LOGIN_KEY']
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ['GOOGLE_LOGIN_SECRET']
Google oauth has one more step than github login. If you just finish this step, you will still get an error. You need to enable Google+ API
On the same page left side, choose ‘library‘, in Social APIs column choose Google+ API: click on enable to enable google+ API.
Add url in page¶
<div class="social-login">
<h4 style="color:black;text-align:center;">- or -</h4>
<div class="social-login-buttons">
<a class="btn btn-block btn-social btn-lg btn-github" id="github_login" href="xxx">
<span class="fa fa-github"></span>Sign in with Github
</a>
<a class="btn btn-block btn-social btn-lg btn-google" href="xxx">
<span class="fa fa-google"></span> Login with Google
</a>
</div>
</div>
Now you can try to login with github and google!
5. User Profile¶
allow user to modify their profile information
Relate submit button with forms¶
There are several submit button in our profile page, and we don‘t want to submit the different form. therefore, we will specify the name of each submit button for each form: in template:
<button class="btn" type="submit" name="changeInfo">Change My Info</button>
in views:
if request.method == 'POST' and 'changeInfo' in request.POST:
note that we use the name of the button in the view.
Obtain data of user specified by fields in the form¶
To get the data specified in the form, take the AdditionalForm
we are using as an example, it has 5 fields: "department" , "organization", "title", "country", "about"
we can easily grab these 5 fields of a user from database by specify the instance
:
model = RegisterUser.objects.get(user=a_user)
infoForm = AdditionalForm(instance=model)
to get the User
model of current user who is using the website, simply use request.user
Views¶
If a user login with social oauth such as google or github, they will not have a RegisterUser model created in the database.
Therefore, RegisterUser.objects.get(user=request.user)
will return error. So when the user go to “Profile“ page, we create one first.
try:
optional = RegisterUser.objects.get(user=user)
except:# If cannot find RegisterUser object(social login users), create one
add_form = AdditionalForm({})
optional = add_form.save(commit=False)
optional.user = user
optional.save()
Then, for the convenience of users, we will get the form with an instance so that their current info will be automatically filled in if we use {{ field }}
as input box in our template.
infoForm = AdditionalForm(instance=optional)#get form with info of a specific instance
if request.method == 'POST' and 'changeInfo' in request.POST:
infoForm = AdditionalForm(request.POST, instance=optional)
if infoForm.is_valid():
infoForm.save()
messages.success(request, 'Your information has been successfully updated!')
else:
infoForm=AdditionalForm(instance=optional)
messages.error(request, 'Something went wrong. Please try again or contact us!')
return redirect('profile')
return render(request, 'app/profile.html', {'infoForm': infoForm,})
Template¶
<h3>Change Profile Information</h3>
<div class="panel-body">
<form method="post">
{% csrf_token %}
{% for field in infoForm %}
<p>
{{ field.label_tag }}<br>
{{ field }}<br>
Current: {{ field.value|linebreaks }}
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
</p>
{% endfor %}
<button class="btn" type="submit" name="changeInfo">Change My Info</button>
</form>
</div>
The linebreaks in {{ field.value|linebreaks }}
will interpret new line character as <br>
and two new line characters as <p>
. Without it, the value in Textarea
may be displayed as a whole paragraph.