Lesson 5A

A Social Blogging Application — Part 3: User Profiles

Share Tweet

Contents

  1. Resources
  2. Part 3: User Profiles
    1. Updating the User table
    2. Implementing the User Profiles page
    3. Profile Editor (User-level)
    4. Profile Editor (Admin-level)
    5. Gravatar
  3. Running Application
  4. Exercises
  5. Further Reading

Resources


Part 3: User Profiles

In this chapter, we will implement a User Profile functionality which allows users to be able to share their profile information publicly. Besides, we also implement a mechanism to allow users to edit their profile by themselves or by the administrator. A user profile will contain some basic information such as name, location, email address, bio, member since, last seen, and avatar. We structure this chapter as follows:

  • We first present the procedure to implement a User Profile functionality;
  • And then, we take the function of editing a user profile as an on-class exercise.

1/ Updating the User table

Some additional information about users will be inserted into the User table, in particular, Fig. 1 presents some updates (from line 10 to 14) to the table.

Fig 1. app/models.py: user information fields
Fig 1. app/models.py: user information fields

The difference between db.String and db.Text is that db.Text is a variable-length field and does not need a maximum length. The two timestamps are given a default value of the current time to the fields of member_since and last_seen. We can see that the member_since field is one-time updated when a user created an account, but the last_seen field must be updated when a user signs in to the application. We thus implement a ping() function to update the last_seen field by taking advantage of the before_request() function in app/auth/views.py, as previously mentioned in Lesson 4 - Part 1: User Authentication. Fig. 1 (line 16 to 19) and 2 present the implementation.

Fig 2. app/auth/views.py: pinging the signed-in user
Fig 2. app/auth/views.py: pinging the signed-in user

2/ Implementing the User Profiles page

In this section, we define the route of the User Profiles page for each user, as shown in Fig. 3. The route is implemented in the main blueprint. For example, if a username is kelvin, the profile page will be at http://localhost:5000/user/kelvin. The idea is that we search the given username in the database, if found, we will render the User Profiles page for this user; otherwise, the 404 error will be returned. The search and error cases can be nicely combined in a single statement using the first_or_404() method. As usual, we also implement the template (HTML) corresponding to this view to render the information, as shown in Fig. 4. We should note that the User Profiles route is not required by the @login_required decorator, it thus makes the User Profiles page publicly accessible.

Fig 3. app/main/views.py: profile page route
Fig 3. app/main/views.py: profile page route
Fig 4. app/templates/user.html: user profile template
Fig 4. app/templates/user.html: user profile template

The link to the User Profiles page in the base.html needs to be checked whether a user is authenticated, since the navigation bar is rendered for both authenticated and unauthenticated users, as shown in Fig. 5.

Fig 5. app/templates/base.html: add link to profile page in the navigation bar
Fig 5. app/templates/base.html: add link to profile page in the navigation bar

To run the application, we need to reconstruct the database since we have updated its structure. A simple way to do this is to delete the current data-dev.sqlite and /migrations directory, and then execute the flask db init and flask db upgrade commands to generate a new database, as mentioned in Lesson 4A/Running your application, and then we insert new records to the User table such as u = User(email='huuphucduong@gmail.com', username='kelvin', password='lovecoding', name='Duong Huu Phuc', location='Ho Chi Minh City, Vietnam', about_me='Lorem Ipsum'). Another option is to execute the flask db stamp [--sql] [--tag TAG] <revision> to set the revision in the database to the one given as an argument, without performing any migrations.

Fig. 6 shows the result when running the application and visit the /user/<username> in a web browser.

Fig 6. The result when visiting /user/username in a web browser
Fig 6. The result when visiting /user/<username> in a web browser

3/ Profile Editor (User-level)

It is obvious that users need to have the ability to edit their profile by themselves. This functionality can be implemented via a web form, in particular, if users want to edit their information, they will visit a web form that presents some editable fields such as name, location, and about me. This form is only accessible when they are authenticated. As usual, we first need to create a web form in main/forms.py to define the form's fields; and then, we implement a function in main/views.py to handle the form's data or to render the form on a web page. To handle the form's data, we employ the validate_on_submit() function to check whether the form's data is submitted in the right format. And to render the form, we implement an HTML file and then map the Flask's render_template() function. Fig. 7, 8, 9 present the implementations in main/forms.py, main/views.py, and the edit_profile.html file. Fig. 10 shows the result when visiting the URL in a web browser.

Fig 7. main/forms.py: Implement the profile editor form (user-level)
Fig 7. main/forms.py: Implement the profile editor form (user-level)
Fig 8. main/views.py: Implement the form's handler and renderer
Fig 8. main/views.py: Implement the form's handler and renderer
Fig 9. templates/edit_profile.html: The HTML template to render the profile editor form
Fig 9. templates/edit_profile.html: The HTML template to render the profile editor form
Fig 10. The result when visiting /my-account/edit-profile in a web browser
Fig 10. The result when visiting /my-account/edit-profile in a web browser

4/ Profile Editor (Admin-level)

As mentioned in the previous section, profile editor is a handy tool for users to allow them to edit their profile by themselves. However, there is some user's information (fields) such as username, member since, user's role that requires an administrator permission to edit the profile. In this section, we will implement the functionality that allows the administrator to edit an user's profile. The profile editing form for administrators is more complex than the one for normal users. In particular, this form allows the administrator to edit all user's information such as username, email, name, role, and so on. Fig. 11 presents the implementation of EditProfileForm_Admin form in app/main/forms.py.

Fig 11. app/main/forms.py: Implementation of EditProfileForm_Admin form
Fig 11. app/main/forms.py: Implementation of EditProfileForm_Admin form

In Fig. 11, we present a SelectField of WTForm to render the drop-down list on HTML to select a user role. SelectField takes a choices parameter which is a list of (value, label) pairs. It can also be a list of only values, in which case the value is used as the label. The value can be any type, but because form data is sent to the browser as strings, you will need to provide a coerce function that converts a string back to the expected type.

As implemented in the Authentication blueprint, the email and username in the form have to be validated whether they have already been in the database, and also checked whether the fields have been updated.

The constructor function of EditProfileForm_Admin form initializes the values for SelectField and a user instance. Since we implement the form constructor, we need to explicitly call the constructor of the superclass in order to populate the fields' inputs.

The route definition for the administrator's profile editor is shown in Fig. 12.

Fig 12. app/main/views.py: Route definition for the administrator's profile editor
Fig 12. app/main/views.py: Route definition for the administrator's profile editor

The route and the implementation of the administrator's profile editor is quite similar to the one of normal users. There are some noteworthy points in Fig. 12:

  • The user id is given as a dynamic argument in the route so the get_or_404(id) expression can return a corresponding user if found, or return the 404 error.
  • The user instance is then passed into the form constructor (line 18) in order to allow the two validation functions in app/main/forms.py to be able to get the user's attributes such as email, username.
  • The role data from the form field can not be directly assigned to the user.role, since their datatype do not match. We thus initialize a Role instance based on the form's data before assigning it to the role attribute of the user instance.
  • Instead of creating a new HTML template, in line 41, we reuse the predefined edit_profile.html, as used in Fig. 8, to render the EditProfileForm_Admin form.

Fig. 13 shows the rendered web page of the administrator's profile editor.

Fig 13. The administrator's profile editor
Fig 13. The administrator's profile editor

We then place the Edit Profile button on the User Profile page to allow the administrator to edit a user's profile. A conditional statement is inserted into the app/templates/user.html to validate whether the current user has the ADMIN privilege to show the Edit Profile button, as shown in Fig. 14.

Fig 14. app/templates/user.html: Modifying user.html to include Edit Profile button for administrator
Fig 14. app/templates/user.html: Modifying user.html to include Edit Profile button for administrator

5/ Gravatar

To increase the awareness of users on the application, we can take advantage of Gravatar which is a public and free profile image hosting service where the users' avatar is associated with their email addresses. The Gravatar API is an easy-to-call API, you just need to hash the email address in MD5 format and then append the hashed string to the Gravatar's URL to obtain the user's avatar. We implement the function to generate an MD5 hash string based on the user's email address and return the user profile image from Gravatar, and then render it on the User Profile page, as shown in Fig. 15 and 16, respectively. The default parameter indicates that if there is no avatar associated with the given email address, it will return a default image which can be an identicon, wavatar, monsterid, retro, robohash or big blue G symbol. The size parameter indicates the image size in pixels. The rating parameter indicates if an image is appropriate for a certain audience, for example, g means that the profile image is suitable for display on all websites with any audience type.

Fig 15. app/models.py: Implementing the MD5 hash generation function
Fig 15. app/models.py: Implementing the MD5 hash generation function
Fig 16. app/templates/user.html: Rendering user profile image
Fig 16. app/templates/user.html: Rendering user profile image

In order to reduce the CPU operations on generating an MD5 hash string for a given email address, we can store the hashed string as an attribute of the User model, since the string will remain constant for as long as the email address is not changed. We thus modify the User model in app/models.py as follows:

  • Including a new attribute, avatar_hash, which stores an MD5 hash string;
  • Implementing a function to calculate and return an MD5 hash string, as shown in Fig. 17;
  • Updating the construct of the User's model to initialize the avatar_hash value if the user's email address is given;
  • Updating the change_email() method to generate a new MD5 hash string when users change their email address.

Fig 17. app/models.py: Update the User model to include avatar_hash attribute
Fig 17. app/models.py: Update the User model to include avatar_hash attribute

☕ Running Application

Since the database structure has been modified, we need to perform the following steps to make sure that your application can run properly:

  • Delete the data-dev.sqlite and /migrations directory.
  • Create database migrations with flask db init command, and it will result in a new /migrations directory placed in the root of the project directory. If this error message Error: No such command 'db' comes up, you should execute the export FLASK_APP=flasky.py command before executing the flask db init.
  • Create a new database and a User instance (optional), since we already have implemented the User Registration procedure. You can create a User instance as follows:
    • flask db upgrade
    • flask shell
    • >>> db.create_all()
    • >>> u = User(email='huuphucduong@gmail.com', username='duonghuuphuc', password='lovecoding', name='Duong Huu Phuc', location='Ho Chi Minh City, Vietnam', about_me='Lorem Ipsum', confirmed=True)
    • >>> db.session.add(u)
    • >>> db.session.commit()
    • >>> quit()
  • It is noteworthy that you must create records for the Role model (table) manually by calling the insert_roles() method in Role class. The following commands will check whether a user has been assigned a role, and then create the records for the Role model:
    • flask shell
    • >>> u = User.query.filter_by(id=1).first() (assume that the database has had this user)
    • >>> print(u.username)
    • duonghuuphuc
    • >>> print(u.role)
    • None
    • >>> Role.insert_roles()
    • >>> u.role = Role.query.filter_by(name='Administrator').first()
    • >>> print(u.role)
    • <Role 'Administrator'>
    • >>> db.session.add(u)
    • >>> db.session.commit()
    • >>> quit()


Exercises

  1. Users need to have an option to edit their profile by accessing a page where they can enter information to present in their profile page. However, not only users have the option to edit their profile, but also the administrator should be able to edit the user's profile, such a use case as some information is not editable by normal users. To implement this exercise, you need to create two different forms to handle each use case, in particular, (i) users change their profiles by themselves, and (ii) administrators change the users' profiles.
  2. Connect the application to Gravatar service to obtain the user's avatar, and then render it on the User Profiles page. Gravatar images can be requested just like a normal image, using an IMG tag. To get an image specific to a user, you just calculate their email hash and append the hashed string to the base URL at https://www.gravatar.com/avatar/<HASH>. You can refer to this document to understand the Gravatar's approach.

Further Reading

  1. Get user's profile image from Facebook
  2. WTForms - Fields

Next Lesson

In the next lesson, we will implement a main feature of our demonstration application which allows users to read and write blog posts.

Lesson 5B: A Social Blogging Application — Part 4: Blog Posts


Relaxing 🎧