In this four-function tutorial series, you're edifice a social network with Django that you lot can showcase in your portfolio. This projection is strengthening your understanding of relationships between Django models and showing you how to use forms so that users can interact with your app and with each other. Y'all're also making your site look proficient past using the Bulma CSS framework.

In the previous part of this series, you lot added functionality so that users can create dweets on the dorsum end and display them on the front end end. At this point, your users can discover and follow other users and read the content of the profiles they follow. They can click a push that sends an HTTP Post request handled by Django to unfollow a profile if they want to stop reading their content.

In the fourth role of this tutorial serial, you'll learn how to:

  • Create and render Django forms from your Dweet model
  • Preclude double submissions and display helpful error letters
  • Interlink pages of your app using dynamic URLs
  • Refactor a view function
  • Use QuerySet field lookups to filter your data on the back end

Once you finish going through this final part of the tutorial, y'all'll have a fully functional basic social network built with Django. It'll permit your users to create short text-based messages, discover and follow other users, and read the content of the profiles they follow. They'll also be able to unfollow a profile if they desire to stop reading their content.

Additionally, yous'll accept showcased that you can use a CSS framework to make your spider web app await great without likewise much extra try.

You can download the code that you'll need to start the concluding function of this project by clicking the link beneath and going to the source_code_start/ folder:

Demo

In this four-office series, you're building a minor social network that allows users to post short text-based letters. The users of your app tin can likewise follow other user profiles to see the posts of these users or unfollow them to cease seeing their text-based posts:

You lot're too learning how to use the CSS framework Bulma to give your app a convenient advent and make information technology a portfolio project you can be proud to testify off.

In the fourth and terminal part of this tutorial serial, you'll learn how to build Django forms on acme of an existing model. You'll also ready and handle more HTTP Postal service request submissions so that your users can post their text-based messages.

At the end of this tutorial, you lot'll have completed your basic social network built with Django. Past then, your users will be able to navigate to a profile list and to private profile pages, follow and unfollow other users, and run across the dweets of the profiles they follow displayed on their dashboard. They'll also be able to submit dweets through a form on their dashboard.

Projection Overview

In this section, you'll get an overview of what topics you'll cover in this last function of the tutorial serial. You'll also get a chance to revisit the full project implementation steps, in case you need to skip dorsum to a previous step that you completed in an earlier part of the serial.

At this point, yous should have finished working through parts ane, 2, and three of this tutorial series. Congratulations! You lot've made your way to the final function, which focuses on edifice forms and handling form submissions:

Once you've implemented the steps of this last part of the series, you lot've completed the bones version of your Django social network. You'll be prepare to take any adjacent steps past yourself to make this project stand out in your web developer portfolio.

To get a high-level idea of how the final part in this series on edifice your Django social network fits into the context of the whole projection, you can expand the collapsible section below:

You're implementing the projection in a number of steps spread out over multiple separate tutorials in this series. There'south a lot to cover, and you're going into details along the manner:

✅ Role 1: Models and Relationships

  • Step 1: Prepare Up the Base Project
  • Step 2: Extend the Django User Model
  • Step 3: Implement a Post-Save Hook

✅ Office 2: Templates and Front end-End Styling

  • Pace 4: Create a Base Template With Bulma
  • Step 5: List All User Profiles
  • Stride vi: Access Individual Profile Pages

✅ Part 3: Follows and Dweets

  • Stride seven: Follow and Unfollow Other Profiles
  • Step 8: Create the Back-End Logic For Dweets
  • Step ix: Display Dweets on the Front

📍 Part 4: Forms and Submissions

  • Step 10: Submit Dweets Through a Django Form
  • Footstep 11: Prevent Double Submissions and Handle Errors
  • Stride 12: Meliorate the Front-End User Experience

Each of these steps will provide links to whatever necessary resources. By budgeted the steps one at a time, you lot'll have the opportunity to pause and come back at a later signal in instance you want to accept a suspension.

With the high-level structure of this tutorial series in mind, yous've got a good idea of where y'all're at and which implementation steps you might have to catch upward on, if you haven't completed them withal.

Before getting started with the adjacent step, accept a quick look at the prerequistes to skim any links to other resources that might be helpful along the way.

Prerequisites

To successfully piece of work through this final part of your projection, you lot demand to have completed the kickoff part on models and relationships, the second role on templates and styling, and the tertiary part on follows and dweets. Please confirm that your project works as described there. You should also exist comfortable with the post-obit concepts:

  • Using object-oriented programming in Python
  • Setting up a basic Django projection
  • Managing routing and redirects, view functions, templates, models, and migrations in Django
  • Using and customizing the Django Admin interface

Make certain that you've completed the beginning iii parts of this series. This final part will pick up correct where you left off at the end of the third role.

You lot tin can also download the code that you'll need for starting the final part of your project past clicking the link below and going to the source_code_start/ folder:

For additional requirements and further links, check out the prerequisites mentioned in the kickoff office of this tutorial series on building a basic social network in Django.

Stride 10: Submit Dweets Using Django Forms

For the sake of this tutorial serial, yous decided early on to handle user creation in your Django admin. Your tiny social network is invite-only, and you lot're the i who decides to create user accounts.

Nonetheless, in one case your users get into your social network app, you lot'll want to requite them the opportunity to create content. They won't have access to the Django admin interface, and your Dwitter volition be barren without any risk for users to create content. Yous'll demand another form as an interface for your users to submit dweets.

Create a Text Input Form

If you're familiar with HTML forms, then you lot might know that you could handle the text submissions by creating another HTML <form> chemical element with specific <input> elements. It would, however, have to wait a flake different from the class that you built for your buttons.

In this tutorial, you'll learn how to create HTML forms using a Django form. You'll write a Django form, and Django will catechumen information technology to an HTML <form> chemical element when rendering the page.

To showtime with this part of the tutorial, create a new file in your Django dwitter app, and phone call information technology forms.py. This file tin can hold all the forms you lot might demand for your projection. You'll only demand a unmarried form and then that your users can submit their dweets:

                                                                      ane                  # dwitter/forms.py                                      2                                      3                  from                  django                  import                  forms                                      iv                  from                  .models                  import                  Dweet                                      v                                      6                  class                  DweetForm                  (                  forms                  .                  ModelForm                  ):                                      7                  trunk                  =                  forms                  .                  CharField                  (                  required                  =                  True                  )                                      8                                      9                  class                  Meta                  :                  10                  model                  =                  Dweet                  11                  exclude                  =                  (                  "user"                  ,                  )                              

In this code snippet, you create DweetForm and inherit from Django's ModelForm. Creating forms in this fashion relies heavily on abstractions gear up by Django, which ways that in this tutorial, you need to ascertain very little by yourself to get a working class:

  • Lines 3 to iv: You import Django'south built-in forms module and the Dweet model that you created in a previous part of this tutorial series.

  • Line 6: Yous create a new form, DweetForm, that inherits from forms.ModelForm.

  • Line 7: You pass the field that y'all want the form to return, and y'all define its blazon. In this case, you want a character field to allow for text input. body is the only field, and you make information technology a required field so that there won't be any empty dweets.

  • Line 9: You create a Meta options class in DweetForm. This options course allows you to pass whatever information that isn't a field to your grade class.

  • Line 10: Yous need to define which model ModelForm should accept its information from. Because yous desire to brand a form that allows users to create dweets, Dweet is the right choice hither.

  • Line 11: By adding the name of the model field that you want to exclude to the exclude tuple, yous ensure that Django will omit information technology when creating the form. Retrieve to add a comma (,) after "user" and so that Python creates a tuple for you lot!

You desire to brand the dweet submissions as user-friendly every bit possible. Users tin simply dweet on your social network when they're logged in, and they can only create dweets for themselves. Therefore, you lot don't need to explicitly laissez passer which user is sending a dweet inside the form.

The setup described in this tutorial holds all the information Django needs to create HTML forms that catch all the info yous need on the front cease. Time to take a wait from that finish.

Return the Form in Your Template

After creating DweetForm in forms.py, y'all can import it in your code logic and send the information to your dashboard template:

                                                  # dwitter/views.py                  from                  django.shortcuts                  import                  render                                      from                    .forms                    import                    DweetForm                                    from                  .models                  import                  Profile                  def                  dashboard                  (                  request                  ):                                      class                    =                    DweetForm                    ()                                                        return                    render                    (                    asking                    ,                    "dwitter/dashboard.html"                    ,                    {                    "form"                    :                    class                    })                                                

With these changes to views.py, you lot first imported DweetForm from forms.py. Then you created a new DweetForm instance, assigned information technology to class, and passed information technology to your dashboard template in your context dictionary under the key "course". This setup allows you to access and return your form in your template:

                                                  <!-- dwitter/templates/dwitter/dashboard.html -->                  {%                  extends                  'base.html'                  %}                  {%                  block                  content                  %}                  <                  div                  course                  =                  "column"                  >                  {%                  for                  followed                  in                  user.contour.follows.all                  %}                  {%                  for                  dweet                  in                  followed.user.dweets.all                  %}                  <                  div                  class                  =                  "box"                  >                  {{                  dweet.body                  }}                  <                  span                  course                  =                  "is-small-scale has-text-grey-light"                  >                  ({{                  dweet.created_at                  }}                  by                  {{                  dweet.user.username                  }}                  </                  bridge                  >                  </                  div                  >                  {%                  endfor                  %}                  {%                  endfor                  %}                  </                  div                  >                                      <                    div                    class                    =                    "column is-one-third"                    >                                                        {{                    form.as_p                    }}                                                        </                    div                    >                                    {%                  endblock                  content                  %}                              

The HTML class that you're assigning to the <div> element uses Bulma'south CSS rules to create a new column on your dashboard page. This extra column makes the page feel less crowded and separates the feed content from the form. Yous then render the Django form with {{ form.as_p }}. Indeed, an input box shows up:

Dashboard showing a plain input box with a label text

This setup shows a minimal display of your Django form. It only has one field, only like yous defined in DweetForm. Withal, it doesn't look good, the text field seems far too small, and there's a characterization reading Body side by side to the input field. You didn't ask for that!

You can improve the display of your Django course past calculation customizations through a widget to forms.CharField in forms.py:

                                                                      one                  # dwitter/forms.py                                      2                                      three                  from                  django                  import                  forms                                      4                  from                  .models                  import                  Dweet                                      5                                      half dozen                  class                  DweetForm                  (                  forms                  .                  ModelForm                  ):                                      7                  body                  =                  forms                  .                  CharField                  (                                      8                  required                  =                  True                  ,                                      ix                                      widget                    =                    forms                    .                    widgets                    .                    Textarea                    (                                    10                                      attrs                    =                    {                                    11                                      "placeholder"                    :                    "Dweet something..."                    ,                                    12                                      "class"                    :                    "textarea is-success is-medium"                    ,                                    13                                      }                                    fourteen                                      ),                                    15                                      label                    =                    ""                    ,                                    16                  )                  17                  18                  class                  Meta                  :                  19                  model                  =                  Dweet                  20                  exclude                  =                  (                  "user"                  ,                  )                              

By adding a Django widget to CharField, you lot go to control a couple of aspects of how the HTML input element will get represented:

  • Line 9: In this line, you lot choose the blazon of input element that Django should use and set it to Textarea. The Textarea widget will render as an HTML <textarea> element, which offers more space for your users to enter their dweets.

  • Lines 10 to thirteen: You further customize Textarea with settings defined in attrs. These settings render to HTML attributes on your <textarea> element.

  • Line 11: Yous add placeholder text that will evidence upwards in the input box and become away once the user clicks on the form field to enter their dweet.

  • Line 12: You add the HTML class "textarea", which relates to a textarea CSS manner rule divers by Bulma and will make your input box more attractive and improve matched to the rest of your folio. You as well add two additional classes, is-success and is-medium, that outline the input field in green and increment the text size, respectively.

  • Line fifteen: You set label to an empty cord (""), which removes the Torso text that previously showed up due to a Django default setting that renders the proper noun of a form field as its label.

With but a few customizations in Textarea, you made your input box fit much better into the existing way of your page:

Dashboard with Dweet Textarea but without a button

The input box looks good, merely it'south not a functional class yet. Did anyone ask for a Submit push?

Brand Class Submissions Possible

Django forms can take the hassle out of creating and styling your form fields. Nevertheless, you all the same need to wrap your Django form into an HTML <form> element and add a button. To create a functional form that allows POST requests, you'll also need to define the HTTP method accordingly:

                                                                      1                  <!-- dwitter/templates/dwitter/dashboard.html -->                                      2                                      3                  {%                  extends                  'base.html'                  %}                                      four                                      v                  {%                  cake                  content                  %}                                      half-dozen                                      7                  <                  div                  class                  =                  "cavalcade"                  >                                      8                  {%                  for                  followed                  in                  user.profile.follows.all                  %}                                      9                  {%                  for                  dweet                  in                  followed.user.dweets.all                  %}                  10                  <                  div                  class                  =                  "box"                  >                  eleven                  {{                  dweet.body                  }}                  12                  <                  span                  form                  =                  "is-small has-text-grey-light"                  >                  xiii                  ({{                  dweet.created_at                  }}                  by                  {{                  dweet.user.username                  }}                  fourteen                  </                  span                  >                  15                  </                  div                  >                  16                  {%                  endfor                  %}                  17                  {%                  endfor                  %}                  xviii                  </                  div                  >                  19                  xx                  <                  div                  course                  =                  "column is-i-3rd"                  >                  21                                      <                    course                    method                    =                    "mail"                    >                                    22                                      {%                    csrf_token                    %}                                    23                  {{                  form.as_p                  }}                  24                                      <                    push                    course                    =                    "push button is-success is-fullwidth is-medium mt-v"                                    25                                      type                    =                    "submit"                    >Dweet                                    26                                      </                    button                    >                                    27                                      </                    class                    >                                    28                  </                  div                  >                  29                  thirty                  {%                  endblock                  content                  %}                              

With another incremental update to your HTML code, y'all completed the front-end setup of your dweet submission class:

  • Lines 21 and 27: You lot wrapped the form code into an HTML <form> element with method set to "post" because you want to send the user-submitted messages via a Mail request.
  • Line 22: You added a CSRF token using the same template tag y'all used when creating the form for post-obit and unfollowing profiles.
  • Lines 24 to 26: You lot completed the form past calculation a button with some Bulma styling through the class attribute, which allows your users to submit the text they entered.

The form looks dainty and seems to be gear up to receive your input:

Dashboard showing an input Textarea box with a submit button

What happens when you lot click the Dweet button? Not much, considering you lot haven't fix any code logic to complement your front-cease lawmaking yet. Your side by side step is to implement the submit functionality in views.py:

                                                                      1                  # dwitter/views.py                                      2                                      iii                  def                  dashboard                  (                  request                  ):                                      iv                  if                  asking                  .                  method                  ==                  "Mail"                  :                                      5                  course                  =                  DweetForm                  (                  request                  .                  POST                  )                                      6                  if                  grade                  .                  is_valid                  ():                                      7                  dweet                  =                  form                  .                  salve                  (                  commit                  =                  Faux                  )                                      8                  dweet                  .                  user                  =                  asking                  .                  user                                      9                  dweet                  .                  salvage                  ()                  10                  class                  =                  DweetForm                  ()                  11                  return                  render                  (                  request                  ,                  "dwitter/dashboard.html"                  ,                  {                  "course"                  :                  form                  })                              

With some additions to dashboard(), you brand it possible for your view to handle the submitted data and create new dweets in your database:

  • Line 4: If a user submits the class with an HTTP POST asking, and so you want to handle that form information. If the view function was called due to an HTTP Become asking, you'll bound right over this whole lawmaking block into line 10 and render the page with an empty class in line xi.

  • Line five: You fill DweetForm with the data that came in through the POST request. Based on your setup in forms.py, Django volition pass the data to body. created_at volition be filled automatically, and y'all explicitly excluded user, which will therefore stay empty for now.

  • Line 6: Django form objects have a method chosen .is_valid(), which compares the submitted data to the expected data defined in the class and the associated model restrictions. If all is well, the method returns Truthful. Yous only allow your code to continue if the submitted class is valid.

  • Line vii: If your class already included all the information it needs to create a new database entry, then you could utilise .save() without any arguments. However, you're still missing the required user entry to associate the dweet with. By adding commit=Fake, you forestall committing the entry to the database yet.

  • Line viii: You option the currently logged-in user object from Django'south request object and relieve information technology to dweet, which you lot created in the previous line. In this way, you've added the missing information by building the clan with the current user.

  • Line ix: Finally, your dweet has all the information it needs, so you tin can successfully create a new entry in the associated table. Yous can at present write the data to your database with .salvage().

  • Line 10 to 11: Whether or non you've handled a POST submission, y'all always pass a new empty DweetForm instance to render(). This function call re-displays the page with a new bare form that's set up for more than of your thoughts.

With that, you've successfully created the text input form and hooked information technology up to your lawmaking logic, so the submissions will exist handled correctly. In this part of the tutorial series, you also got to know Django forms. Yous rendered a form in your template, and so applied Bulma styling to information technology past customizing attributes in a Textarea widget.

Earlier you lot're ready to open up up your Django social network to real-life users, there is, nonetheless, one issue you lot demand to address. If you write a dweet and submit it now, it gets added all correct, but if you reload the page later submitting, the same dweet will get added once again!

Step 11: Prevent Double Submissions and Handle Errors

At this betoken, you can create new dweets through your app's front stop and view your own dweets together with the dweets of the profiles you follow on your dashboard. At the cease of this footstep, you'll have prevented double dweet submissions and learned how Django displays errors with the text input.

But first, you should get an idea of what the trouble is. Go to your dashboard, write an inspiring dweet, and click on Dweet to submit information technology. Y'all'll see it show up in your listing of displayed dweets in your timeline, and the dweet class will show upward as empty again.

Without doing anything else, reload the folio with a keyboard shortcut:

  • Cmd + R on macOS
  • Ctrl + R on Windows and Linux

Your browser might prompt you with a pop-upwardly that asks whether you lot desire to transport the form again. If this message shows upwards, confirm past pressing Send. At present you'll observe that the same dweet you sent before appears a second fourth dimension on your dashboard. You can continue doing this as many times as you desire to:

Dashboard showing the same Dweet many times because of the double-submission bug

Later posting a dweet, Django sends another Mail service request with the same information and creates some other entry in your database if you reload the page. You'll run across the dweet pop upwardly a second time. And a third time. And a fourth time. Django volition go on making duplicate dweets equally ofttimes as you keep reloading. You lot don't want that!

Forbid Double Submissions

To avoid double dweet submission, you'll have to foreclose your app from keeping the request data around, so that a reload won't take the chance to resubmit the data. You can do just that past using a Django redirect:

                                                  # dwitter/views.py                                      from                    django.shortcuts                    import                    render                    ,                    redirect                                    # ...                  def                  dashboard                  (                  request                  ):                  if                  request                  .                  method                  ==                  "POST"                  :                  course                  =                  DweetForm                  (                  request                  .                  Post                  )                  if                  class                  .                  is_valid                  ():                  dweet                  =                  form                  .                  salve                  (                  commit                  =                  False                  )                  dweet                  .                  user                  =                  asking                  .                  user                  dweet                  .                  salve                  ()                                      render                    redirect                    (                    "dwitter:dashboard"                    )                                    form                  =                  DweetForm                  ()                  return                  render                  (                  request                  ,                  "dwitter/dashboard.html"                  ,                  {                  "form"                  :                  form                  })                              

By importing redirect() and returning a call to it afterwards successfully adding a newly submitted dweet to your database, yous ship the user dorsum to the same folio. Even so, this time you're sending a Go request when redirecting, which means that whatever number of page reloads volition but always bear witness the dweets that already exist instead of creating an ground forces of cloned dweets.

You ready this up by referencing the app_name variable and the name keyword statement of a path(), which you defined in your URL configuration:

  • "dwitter" is the app_name variable that describes the namespace of your app. You can find it before the colon (:) in the cord argument passed to redirect().
  • "dashboard" is the value of the proper noun keyword argument for the path() entry that points to dashboard(). You need to add together it later the colon (:) in the cord argument passed to redirect().

To use redirect() as shown higher up, y'all need to ready the namespacing in dwitter/urls.py accordingly, which you lot did in a previous part of the tutorial series:

                                                  # dwitter/urls.py                  # ...                                      app_name                    =                    "dwitter"                                    urlpatterns                  =                  [                                      path                    (                    ""                    ,                    dashboard                    ,                    name                    =                    "dashboard"                    ),                                    # ...                              

With urls.py set up as shown in a higher place, you can use redirect() to bespeak your users dorsum to their dashboard page with a Become asking after successfully processing the POST request from their form submission.

After you return redirect() at the terminate of your conditional statement, any reloads only load the page without resubmitting the form. Your users can at present safely submit curt dweets without unexpected results. However, what happens when a dweet goes beyond the 140 character limit?

Effort typing a long dweet that goes over the 140 graphic symbol limit and submit it. What happens? Nothing! Just at that place's also no error bulletin, so your users might not even know that they did something wrong.

Additionally, the text you entered is gone, a major annoyance in poorly designed user forms. And then you might want to make this feel ameliorate for your users past notifying them about what they did wrong and keeping the text they entered!

Handle Submission Errors

You defined in your models that your text-based messages can have a maximum length of 140 characters, and you're enforcing this when users submit their text. However, you're not telling them when they exceed the character limit. When they submit a dweet that's too long, their input is lost.

The good news is that you lot can use Django forms rendered with {{ class.as_p }} to display error messages that get sent along with the form object without needing to add any code. These fault messages can improve the user experience significantly.

Merely currently, you can't encounter any error messages, so why is that? Take some other look at dashboard():

                                                                      1                  # dwitter/views.py                                      2                                      3                  # ...                                      4                                      5                  def                  dashboard                  (                  asking                  ):                                      6                  if                  request                  .                  method                  ==                  "POST"                  :                                      vii                                      form                    =                    DweetForm                    (                    request                    .                    POST                    )                                                        viii                  if                  grade                  .                  is_valid                  ():                                      9                  dweet                  =                  form                  .                  save                  (                  commit                  =                  False                  )                  x                  dweet                  .                  user                  =                  request                  .                  user                  xi                  dweet                  .                  save                  ()                  12                  return                  redirect                  (                  "dwitter:dashboard"                  )                  13                                      form                    =                    DweetForm                    ()                                    14                  render                  render                  (                  request                  ,                  "dwitter/dashboard.html"                  ,                  {                  "form"                  :                  form                  })                              

In the highlighted lines, you can meet that you lot're creating ane of two different DweetForm objects, either a bound or an unbound class:

  1. Line 7: If your role gets called from a Post request, you instantiate DweetForm with the data that came along with the asking. Django creates a leap form that has access to data and can get validated.
  2. Line 13: If your page gets chosen with a Get request, you're instantiating an unbound form that doesn't have any data associated with it.

This setup worked fine and made sense upwards to now. You lot want to display an empty class if a user accesses the page past navigating there, and you want to validate and handle the submitted data in your form if a user writes a dweet and sends it to the database.

However, the crux is in the details hither. Yous can—and should—validate the bound form, which yous do in line eight. If the validation passes, the dweet gets written to your database. Still, if a user adds too many characters, so your course validation fails, and the code in your conditional statement doesn't get executed.

Python jumps execution to line 13, where you overwrite form with an empty unbound DweetForm object. This form is what gets sent to your template and rendered. Since you overwrote the spring course that held the information near the validation fault with an unbound form, Django won't display any of the validation errors that occurred.

To transport the bound class to the template if a validation error occurs, y'all need to change your code slightly:

                                                  # dwitter/views.py                  # ...                  def                  dashboard                  (                  request                  ):                                      form                    =                    DweetForm                    (                    asking                    .                    Mail                    or                    None                    )                                    if                  request                  .                  method                  ==                  "POST"                  :                  if                  form                  .                  is_valid                  ():                  dweet                  =                  form                  .                  save                  (                  commit                  =                  False                  )                  dweet                  .                  user                  =                  asking                  .                  user                  dweet                  .                  save                  ()                  return                  redirect                  (                  "dwitter:dashboard"                  )                  return                  return                  (                  asking                  ,                  "dwitter/dashboard.html"                  ,                  {                  "form"                  :                  form                  })                              

With this change, you removed the duplicate instantiation of DweetForm so that at that place's only ever one form that'll get passed to your template, whether the user submitted a valid form or not.

The syntax that you lot used for this change might expect unfamiliar. So here'southward what's going on:

  • POST asking: If you call dashboard() with a Postal service request that includes any data, the request.Postal service QueryDict will contain your grade submission data. The request.POST object now has a truthy value, and Python will curt-excursion the or operator to return the value of request.POST. This manner, you'll pass the form content every bit an argument when instantiating DweetForm, as y'all did previously with form = DweetForm(request.POST).

  • Get request: If you lot telephone call dashboard() with a GET asking, request.POST will exist empty, which is a falsy value. Python will continue evaluating the or expression and render the 2nd value, None. Therefore, Django will instantiate DweetForm every bit an unbound form object, like y'all previously did with form = DweetForm().

The advantage of this setup is that y'all now pass the bound form to your template even when the grade validation fails, which allows Django's {{ form.as_p }} to render a descriptive error bulletin for your users out of the box:

Dashboard showing a form error message sent by Django when attempting to submit a Dweet that exceeds the character limit

Afterwards submitting text that exceeds the grapheme limit that you defined in Dweet, your users volition encounter a descriptive mistake message pop up correct above the form input field. This message gives them feedback that their dweet hasn't been submitted, provides information about why that happened, and fifty-fifty gives information about how many characters their current text has.

The all-time thing about this change is that y'all're passing the bound course object that retains the text information that your user entered in the form. No information is lost, and they tin use the helpful suggestions to edit their dweet and submit information technology to the database successfully.

Stride 12: Improve the Front end-End User Feel

At this indicate, yous have a functional social media app that you built with the Django spider web framework. Your users can mail service text-based messages, follow and unfollow other user profiles, and see dweets on their dashboard view. At the stop of this stride, you'll accept improved your app's user experience past adding additional navigation links and sorting the dweets to brandish the newest dweets showtime.

Improve the Navigation

Your social network has three different pages that your users might want to visit at unlike times:

  1. The empty URL path (/) points to the dashboard page.
  2. The /profile_list URL path points to the listing of profiles.
  3. The /profile/<int> URL path points to a specific user'south profile page.

Your users can already access all of these pages through their respective URL slugs. Even so, while your users tin can, for example, access a contour page by clicking on the username card from the list of all profiles, there'southward currently no straightforward navigation to access the profile list or the dashboard page. It'due south time to add some more than links so that users can conveniently movement between the dissimilar pages of your web app.

Head back to your templates binder and open dashboard.html. Add two buttons above the dweet form to allow your users to navigate to different pages in your app:

  1. The profile list folio
  2. Their personal profile folio

You can employ the dynamic URL pattern with Django's {% url %} tags that you've used before:

                                                  <!-- dwitter/templates/dwitter/dashboard.html -->                  <!-- ... -->                  <                  div                  class                  =                  "block"                  >                  <                  a                  href                  =                  "                  {%                  url                  'dwitter:profile_list'                  %}                                      "                  >                  <                  button                  class                  =                  "button is-dark is-outlined is-fullwidth"                  >                  All Profiles                  </                  button                  >                  </                  a                  >                  </                  div                  >                  <                  div                  class                  =                  "cake"                  >                  <                  a                  href                  =                  "                  {%                  url                  'dwitter:profile'                  asking.user.contour.id                  %}                                      "                  >                  <                  button                  form                  =                  "button is-success is-light is-outlined is-fullwidth"                  >                  My Profile                  </                  push                  >                  </                  a                  >                  </                  div                  >                  <!-- ... -->                              

You tin add this code as the first two elements inside <div class="column is-one-third">. You lot can also add a heading just above your dweet form to explain more clearly what the form is for:

                                                  <!-- dwitter/templates/dwitter/dashboard.html -->                  <!-- ... -->                                      <                    div                    form                    =                    "block"                    >                                                        <                    div                    course                    =                    "cake"                    >                                                        <                    h2                    course                    =                    "title is-2"                    >Add together a Dweet</                    p                    >                                                        </                    div                    >                                                        <                    div                    class                    =                    "block"                    >                                    <                  form                  method                  =                  "post"                  >                  {%                  csrf_token                  %}                  {{                  form.as_p                  }}                  <                  button                  class                  =                  "push button is-success is-fullwidth is-medium mt-5"                  blazon                  =                  "submit"                  >Dweet                  </                  button                  >                  </                  form                  >                                      </                    div                    >                                                        </                    div                    >                                    <!-- ... -->                              

With these two additions, you used the "block" course to adjust the three <div> elements on top of 1 some other, and you added sensible navigation buttons that enhance the user experience on your dashboard page:

Finished Dashboard page that shows followed dweets on the left, and a Dweet form with navigation buttons on the right.

Later on calculation all these changes, your dashboard template will be complete. You can compare the code that you wrote to the template below:

                                                  <!-- dwitter/templates/dwitter/dashboard.html -->                  {%                  extends                  'base.html'                  %}                  {%                  block                  content                  %}                  <                  div                  class                  =                  "column"                  >                  {%                  for                  followed                  in                  user.contour.follows.all                  %}                  {%                  for                  dweet                  in                  followed.user.dweets.all                  %}                  <                  div                  course                  =                  "box"                  >                  {{                  dweet.body                  }}                  <                  span                  class                  =                  "is-modest has-text-gray-light"                  >                  ({{                  dweet.created_at                  }}                  by                  {{                  dweet.user.username                  }}                  </                  bridge                  >                  </                  div                  >                  {%                  endfor                  %}                  {%                  endfor                  %}                  </                  div                  >                  <                  div                  class                  =                  "column is-1-tertiary"                  >                  <                  div                  form                  =                  "block"                  >                  <                  a                  href                  =                  "                  {%                  url                  'dwitter:profile_list'                  %}                                      "                  >                  <                  button                  class                  =                  "button is-dark is-outlined is-fullwidth"                  >                  All Profiles                  </                  button                  >                  </                  a                  >                  </                  div                  >                  <                  div                  form                  =                  "block"                  >                  <                  a                  href                  =                  "                  {%                  url                  'dwitter:profile'                  request.user.contour.id                  %}                                      "                  >                  <                  push                  course                  =                  "button is-success is-light is-outlined is-fullwidth"                  >                  My Profile                  </                  button                  >                  </                  a                  >                  </                  div                  >                  <                  div                  form                  =                  "block"                  >                  <                  div                  class                  =                  "cake"                  >                  <                  h2                  class                  =                  "title is-two"                  >Add a Dweet</                  p                  >                  </                  div                  >                  <                  div                  class                  =                  "cake"                  >                  <                  form                  method                  =                  "post"                  >                  {%                  csrf_token                  %}                  {{                  form.as_p                  }}                  <                  button                  form                  =                  "button is-success is-fullwidth is-medium mt-5"                  blazon                  =                  "submit"                  >Dweet                  </                  button                  >                  </                  form                  >                  </                  div                  >                  </                  div                  >                  </                  div                  >                  {%                  endblock                  content                  %}                              

Your dashboard page is functional and looks great! That's important because it'll probable exist the page where your users will spend almost of their time when they interact with your social network. Therefore, you should as well give your users aplenty possibilities to get back to the dashboard page after they've navigated, for instance, to their profile folio.

To brand this possible, you tin add a link to the dashboard page correct at the top of all of your pages by calculation it to the app header that y'all wrote in base of operations.html:

                                                  <!-- templates/base of operations.html -->                  <!-- ... -->                                      <                    a                    href                    =                    "                    {%                    url                    'dwitter:dashboard'                    %}                                          "                    >                                    <                  department                  course                  =                  "hero is-small-scale is-success mb-4"                  >                  <                  div                  form                  =                  "hero-trunk"                  >                  <                  h1                  class                  =                  "championship is-1"                  >Dwitter</                  h1                  >                  <                  p                  class                  =                  "subtitle is-4"                  >                  Your tiny social network built with Django                  </                  p                  >                  </                  div                  >                  </                  section                  >                                      </                    a                    >                                    <!-- ... -->                              

By wrapping the HTML <section> element in a link element, you made the whole hero clickable and gave your users a quick way to return to their dashboard page from anywhere in the app.

With these updated links, you lot've improved the user experience of your app significantly. Finally, if your users desire to stay up to engagement with what their network is dweeting, you'll want to change the dweet brandish to show the newest dweets first, independent of who wrote the text.

Sort the Dweets

There are a couple of ways that you could sort the dweets, and a few places where y'all could practise that, namely:

  1. In your model
  2. In your view function
  3. In your template

Up to now, you've built quite a chip of your code logic inside of your dashboard template. Merely there's a reason for the separation of concerns. Equally you'll learn below, you lot should handle most of your app'due south code logic in the views.

If you wanted to sort the dweets to brandish the newest dweet offset, contained of who wrote the dweet, you might scratch your head virtually how to do this with the nested for loop syntax that you're currently using in your dashboard template.

Do you lot know why this might go difficult? Head over to dashboard.html and inspect the current setup:

                                                        {%                    for                    followed                    in                    user.profile.follows.all                    %}                    {%                    for                    dweet                    in                    followed.user.dweets.all                    %}                    <                    div                    class                    =                    "box"                    >                    {{                    dweet.body                    }}                    <                    span                    class                    =                    "is-small has-text-grey-low-cal"                    >                    ({{                    dweet.created_at                    }}                    past                    {{                    dweet.user.username                    }})                    </                    span                    >                    </                    div                    >                    {%                    endfor                    %}                    {%                    endfor                    %}                                  

How would you try to approach the sorting with this setup? Where do you think you lot might run into difficulties, and why? Take a moment and pull out your pencil and your notebook. Liberally make use of your preferred search engine and run across if you tin can come up with a solution or explain why this might be challenging to solve.

Instead of handling and so much of your lawmaking logic in your template, it's a better idea to exercise this right inside dashboard() and pass the ordered consequence to your template for display.

So far, you've used view functions that handle only the form submission and otherwise define which template to render. Yous didn't write whatever additional logic to decide which information to fetch from the database.

In your view functions, you lot can apply Django ORM calls with modifiers to become precisely the dweets yous're looking for.

You'll fetch all the dweets from all the profiles that a user follows right inside your view function. And then you'll sort them by engagement and fourth dimension and pass a new sorted iterable named dweet to your template. You'll use this iterable to display all these dweets in a timeline ordered from newest to oldest:

                                                                      ane                  # dwitter/views.py                                      2                                      three                  from                  django.shortcuts                  import                  return                  ,                  redirect                                      4                  from                  .forms                  import                  DweetForm                                      5                                      from                    .models                    import                    Dweet                    ,                    Profile                                                        vi                                      7                  def                  dashboard                  (                  request                  ):                                      8                  course                  =                  DweetForm                  (                  request                  .                  POST                  or                  None                  )                                      nine                  if                  request                  .                  method                  ==                  "Mail service"                  :                  10                  if                  form                  .                  is_valid                  ():                  eleven                  dweet                  =                  course                  .                  save                  (                  commit                  =                  Fake                  )                  12                  dweet                  .                  user                  =                  request                  .                  user                  13                  dweet                  .                  save                  ()                  fourteen                  return                  redirect                  (                  "dwitter:dashboard"                  )                  15                  16                                      followed_dweets                    =                    Dweet                    .                    objects                    .                    filter                    (                                    17                                      user__profile__in                    =                    asking                    .                    user                    .                    contour                    .                    follows                    .                    all                    ()                                    18                                      )                    .                    order_by                    (                    "-created_at"                    )                                    19                  20                  return                  render                  (                  21                  request                  ,                  22                  "dwitter/dashboard.html"                  ,                  23                                      {                    "form"                    :                    form                    ,                    "dweets"                    :                    followed_dweets                    },                                    24                  )                              

In this update to dashboard(), yous brand a couple of changes that deserve further attending:

  • Line v: You add an import for the Dweet model. Until at present, you didn't need to accost any dweet objects in your views because you were treatment them in your template. Since yous want to filter them now, you need admission to your model.

  • Line 16: In this line, you utilize .filter() on Dweet.objects, which allows you to pick particular dweet objects from the table depending on field lookups. You save the output of this phone call to followed_dweets.

  • Line 17 (keyword): First, y'all ascertain the queryset field lookup, which is Django ORM syntax for the chief part of an SQL WHERE clause. You lot can follow through database relations with a double-underscore syntax (__) specific to Django ORM. You lot write user__profile__in to access the contour of a user and come across whether that profile is in a drove that you'll laissez passer as the value to your field lookup keyword argument.

  • Line 17 (value): In the second part of this line, y'all provide the second office of the field lookup. This role needs to exist a QuerySet object containing contour objects. You can fetch the relevant profiles from your database past accessing all contour objects in .follows of the currently logged-in user's profile (asking.user.profile).

  • Line eighteen: In this line, you chain some other method phone call to the event of your database query and declare that Django should sort the dweets in descending order of created_at.

  • Line 23: Finally, y'all add a new entry to your context dictionary, where you pass followed_dweets. The followed_dweets variable contains a QuerySet object of all the dweets of all the profiles the current user follows, ordered by the newest dweet first. You're passing it to your template under the primal name dweets.

You can now update the template in dashboard.html to reverberate these changes and reduce the amount of code logic that yous need to write in your template, effectively getting rid of your nested for loop:

                                                  <!-- dwitter/templates/dwitter/dashboard.html -->                  <!-- ... -->                  {%                  for                  dweet                  in                  dweets                  %}                  <                  div                  form                  =                  "box"                  >                  {{                  dweet.body                  }}                  <                  span                  course                  =                  "is-small has-text-gray-lite"                  >                  ({{                  dweet.created_at                  }}                  past                  {{                  dweet.user.username                  }}                  </                  span                  >                  </                  div                  >                  {%                  endfor                  %}                  <!-- ... -->                              

You've fabricated the pre-selected and pre-sorted dweets available to your template under the name dweets. At present you can iterate over that QuerySet object with a single for loop and access the dweet attributes without needing to step through whatsoever model relationships in your template.

Go ahead and reload the page afterwards making this change. Yous can at present see all the dweets of all the users yous follow, sorted with the newest dweets up top. If you lot add together a new dweet while following your own account, information technology'll appear correct at the height of this list:

Dashboard dweets showing the newest dweets at the top

This change completes the terminal updates y'all need to make and so that your Django social media app provides a user-friendly experience. You tin can now declare your Django social media app feature-complete and offset inviting users.

Conclusion

In this tutorial, y'all built a pocket-sized social network using Django. Your app users can follow and unfollow other user profiles, post short text-based letters, and view the messages of other profiles they follow.

In the process of building this project, you've learned how to:

  • Build a Django projection from outset to end
  • Implement OneToOne and ForeignKey relationships between Django models
  • Extend the Django user model with a custom Profile model
  • Customize the Django admin interface
  • Integrate Bulma CSS to style your app

You've covered a lot of basis in this tutorial and built an app that you can share with your friends and family. Y'all can also display it as a portfolio projection for potential employers.

Yous can download the final code for this project by clicking the link below and going to the source_code_final/ folder:

Adjacent Steps

If you've already created a portfolio site, add together your project there to showcase your work. You can go along improving your Django social network to add functionality and make it even more impressive.

Here are some ideas to take your project to the next level:

  • Implement User Authentication: Let new users to sign up through the front end of your web app by following the steps outlined in Get Started With Django Role 2: Django User Direction.
  • Deploy Your Dwitter Project: Put your spider web app online for the whole globe to see by hosting your Django projection on Heroku.
  • Go Social: Invite your friends to join your Django social network, and showtime dweeting your thoughts to one another.

What other ideas can you come up up with to extend this project? Share your project links and ideas for further development in the comments below!