Extending the user model
When you have to deal with user accounts, you will find that the user model of the Django authentication framework is suitable for common cases. However, the user model comes with very basic fields. You may wish to extend the user model to include additional data. The best way to do this is by creating a profile model that contains all additional fields and a one-to-one relationship with the Django user model.
Edit the models.py file of your account application and add the following code to it:
from django.db import models
from django.conf import settings
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
date_of_birth = models.DateField(blank=True, null=True)
photo = models.ImageField(upload_to='users/%Y/%m/%d/',
blank=True)
def __str__(self):
return 'Profile for user {}'.format(self.user.username)
In order to keep your code generic, use the get_user_model() method to retrieve the user model and the AUTH_USER_MODEL setting to refer to it when defining a model's relations to the user model, instead of referring to the auth user model directly.
The user one-to-one field allows you to associate profiles with users. We use CASCADE for the on_delete parameter so that its related profile also gets deleted when a user is deleted. The photo field is an ImageField field. You will need to install the Pillow library to handle images. Install Pillow by running the following command in your shell:
pip install Pillow==5.1.0
For Django to serve media files uploaded by users with the development server, add the following settings to the settings.py file of your project:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL is the base URL to serve the media files uploaded by users, and MEDIA_ROOT is the local path where they reside. We build the path dynamically relative to our project path to make our code more generic.
Now, edit the main urls.py file of the bookmarks project and modify the code, as follows:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('account.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
In this way, the Django development server will be in charge of serving the media files during development (that is when the DEBUG setting is set to True).
The static() helper function is suitable for development, but not for production use. Never serve your static files with Django in a production environment.
Open the shell and run the following command to create the database migration for the new model:
python manage.py makemigrations
You will get the following output:
Migrations for 'account':
account/migrations/0001_initial.py
- Create model Profile
Next, sync the database with the following command:
python manage.py migrate
You will see an output that includes the following line:
Applying account.0001_initial... OK
Edit the admin.py file of the account application and register the Profile model in the administration site, like this:
from django.contrib import admin
from .models import Profile
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
list_display = ['user', 'date_of_birth', 'photo']
Run the development server using the python manage.py runserver command and open http://127.0.0.1:8000/admin/ in your browser. Now, you should be able to see the Profiles model in the administration site of your project, as follows:
Now, we will let users edit their profile on the website. Add the following import and model forms to the forms.py file of the account application:
from .models import Profile
class UserEditForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
class ProfileEditForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('date_of_birth', 'photo')
These forms are as follows:
- UserEditForm: This will allow users to edit their first name, last name, and email, which are attributes of the built-in Django user model.
- ProfileEditForm: This will allow users to edit the profile data we save in the custom Profile model. Users will be able to edit their date of birth and upload a picture for their profile.
Edit the views.py file of the account application and import the Profile model, like this:
from .models import Profile
Then, add the following lines to the register view below new_user.save():
# Create the user profile
Profile.objects.create(user=new_user)
When users register on our site, we will create an empty profile associated with them. You should create a Profile object manually using the administration site for the users you created before.
Now, we will let users edit their profile. Add the following code to the same file:
from .forms import LoginForm, UserRegistrationForm, \
UserEditForm, ProfileEditForm
@login_required
def edit(request):
if request.method == 'POST':
user_form = UserEditForm(instance=request.user,
data=request.POST)
profile_form = ProfileEditForm(
instance=request.user.profile,
data=request.POST,
files=request.FILES)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
else:
user_form = UserEditForm(instance=request.user)
profile_form = ProfileEditForm(
instance=request.user.profile)
return render(request,
'account/edit.html',
{'user_form': user_form,
'profile_form': profile_form})
We use the login_required decorator because users have to be authenticated to edit their profile. In this case, we are using two model forms: UserEditForm to store the data of the built-in user model and ProfileEditForm to store the additional profile data in the custom Profile model. To validate the submitted data, we will execute the is_valid() method of both forms. If both forms contain valid data, we will save both forms, calling the save() method to update the corresponding objects in the database.
Add the following URL pattern to the urls.py file of the account application:
path('edit/', views.edit, name='edit'),
Finally, create a template for this view in templates/account/ and name it edit.html. Add the following code to it:
{% extends "base.html" %}
{% block title %}Edit your account{% endblock %}
{% block content %}
<h1>Edit your account</h1>
<p>You can edit your account using the following form:</p>
<form action="." method="post" enctype="multipart/form-data">
{{ user_form.as_p }}
{{ profile_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Save changes"></p>
</form>
{% endblock %}
We include enctype="multipart/form-data" in our form to enable file uploads. We use an HTML form to submit both the user_form and the profile_form forms.
Register a new user and open http://127.0.0.1:8000/account/edit/. You should see the following page:
Now, you can also edit the dashboard page and include links to the edit profile and change password pages. Open the account/dashboard.html template:
<p>Welcome to your dashboard.</p>
Replace the preceding line with the following one:
<p>Welcome to your dashboard. You can <a href="{% url "edit" %}">edit your profile</a> or <a href="{% url "password_change" %}">change your password</a>.</p>
Users can now access the form to edit their profile from their dashboard. Open http://127.0.0.1:8000/account/ in your browser and test the new link to edit the user's profile: