<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Learn Django</title>
    <description>The latest articles on Forem by Learn Django (@learndjango).</description>
    <link>https://forem.com/learndjango</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1848%2F95bade4d-f501-4562-91a1-0a63b81f6b09.png</url>
      <title>Forem: Learn Django</title>
      <link>https://forem.com/learndjango</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/learndjango"/>
    <language>en</language>
    <item>
      <title>Django Email and Contact Form Tutorial</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Fri, 26 Jan 2024 17:47:00 +0000</pubDate>
      <link>https://forem.com/learndjango/django-email-and-contact-form-tutorial-mbf</link>
      <guid>https://forem.com/learndjango/django-email-and-contact-form-tutorial-mbf</guid>
      <description>&lt;p&gt;Let's build a simple contact form that sends email from a Django 5.0 website. We can take advantage of Django's built-in &lt;a href="https://docs.djangoproject.com/en/5.0/topics/email/" rel="noopener noreferrer"&gt;email support&lt;/a&gt; to make this relatively painless by first outputting emails to our terminal and then sending them in production using an email service like SendGrid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial setup
&lt;/h2&gt;

&lt;p&gt;The first step is to create a dedicated directory for our code. A convenient location is the Desktop. From the command line, execute the following commands to navigate to the Desktop and create a new &lt;code&gt;contact&lt;/code&gt; folder with either Windows or macOS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Windows
$ cd onedrive\desktop\code
$ mkdir contact
$ cd contact

# macOS
$ cd ~/desktop/code
$ mkdir contact
$ cd contact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can create and activate a virtual environment called &lt;code&gt;.venv&lt;/code&gt;. Then, install the latest version of Django.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Windows
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $ python -m pip install django~=5.0

# macOS
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ python3 -m pip install django~=5.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's create a new Django project called &lt;code&gt;django_project&lt;/code&gt; and within it an app called &lt;code&gt;sendemail&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ django-admin startproject django_project .
(.venv) $ python manage.py startapp sendemail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure everything is installed correctly, let's &lt;code&gt;migrate&lt;/code&gt; the initial database and execute &lt;code&gt;runserver&lt;/code&gt; to start the local web server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ python manage.py migrate
(.venv) $ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you open you browser to &lt;a href="http://127.0.0.1:8000/" rel="noopener noreferrer"&gt;127.0.0.1:8000&lt;/a&gt; you should see the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfzha13b61xzhmq4e6h1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfzha13b61xzhmq4e6h1.png" alt="Django welcome page" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Update settings.py
&lt;/h2&gt;

&lt;p&gt;We've created a new app, so we must explicitly add it to our Django project. Within your &lt;code&gt;settings.py&lt;/code&gt; file, under &lt;code&gt;INSTALLED_APPS&lt;/code&gt;, add &lt;code&gt;sendemail&lt;/code&gt; at the bottom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sendemail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.djangoproject.com/en/5.0/topics/email/#send-mail" rel="noopener noreferrer"&gt;send_email&lt;/a&gt; function is what sends our emails, and its required parameters include &lt;code&gt;subject,&lt;/code&gt; &lt;code&gt;message,&lt;/code&gt; &lt;code&gt;from_email,&lt;/code&gt; and &lt;code&gt;recipient_list.&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;At the bottom of the same &lt;code&gt;settings.py&lt;/code&gt; file, specify the &lt;code&gt;EMAIL_BACKEND&lt;/code&gt; we'll use, which should be &lt;code&gt;console&lt;/code&gt; for local development. We also need to add a &lt;a href="https://docs.djangoproject.com/en/5.0/ref/settings/#default-from-email" rel="noopener noreferrer"&gt;DEFAULT_FROM_EMAIL&lt;/a&gt;. And since &lt;a href="https://docs.djangoproject.com/en/5.0/topics/email/#send-mail" rel="noopener noreferrer"&gt;send_email&lt;/a&gt; requires a &lt;code&gt;recipient_list&lt;/code&gt;, we will add a new setting, &lt;code&gt;NOTIFY_EMAIL&lt;/code&gt; for that purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.core.mail.backends.console.EmailBackend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;will@learndjango.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;NOTIFY_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;will@learndjango.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update urls.py
&lt;/h2&gt;

&lt;p&gt;Since we've added an app to our Django project, we need to update the root &lt;code&gt;django_project/urls.py&lt;/code&gt; file, importing &lt;code&gt;include&lt;/code&gt; at the top and listing a new urlpattern for the app at the empty string, &lt;code&gt;""&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sendemail.urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a new file called &lt;code&gt;sendemail/urls.py&lt;/code&gt; and add the following code so that the main contact form is at &lt;code&gt;contact/&lt;/code&gt; and a successful submission redirects to &lt;code&gt;success/&lt;/code&gt;. We have yet to create the corresponding views: &lt;code&gt;SuccessView&lt;/code&gt; and &lt;code&gt;ContactView&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sendemail/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SuccessView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ContactView&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contact/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ContactView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contact&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SuccessView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create forms.py
&lt;/h2&gt;

&lt;p&gt;Still, within our &lt;code&gt;sendemail&lt;/code&gt; app, create a new file named &lt;code&gt;sendemail/forms.py.&lt;/code&gt; It will have a &lt;code&gt;ContactForm&lt;/code&gt; class containing the fields in our contact form: &lt;code&gt;from_email&lt;/code&gt;, &lt;code&gt;subject&lt;/code&gt;, and &lt;code&gt;message&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sendemail/forms.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EmailField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;placeholder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your e-mail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;placeholder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Textarea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;placeholder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're using Django's built-in &lt;a href="https://docs.djangoproject.com/en/5.0/ref/forms/api/" rel="noopener noreferrer"&gt;Forms API&lt;/a&gt; to create three fields and add placeholder text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create views.py
&lt;/h2&gt;

&lt;p&gt;Now, let's create the view that will do the bulk of the work for our contact form. Update the existing &lt;code&gt;sendemail/views.py&lt;/code&gt; file, and we'll review the code below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sendemail/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.core.mail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_mail&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FormView&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.forms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ContactForm&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SuccessView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FormView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;form_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContactForm&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contact.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_success_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contact&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;full_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            Received message below from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
            ________________________


            &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received contact form submission&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;full_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;from_email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;recipient_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFY_EMAIL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ContactView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing we do is create a &lt;code&gt;SuccessView&lt;/code&gt; that displays a template, &lt;code&gt;success.html&lt;/code&gt;. The &lt;code&gt;ContactView&lt;/code&gt; is where the real work happens: it extends Django's &lt;a href="https://docs.djangoproject.com/en/5.0/ref/class-based-views/generic-editing/#formview" rel="noopener noreferrer"&gt;FormView&lt;/a&gt; and refers to the corresponding form, &lt;code&gt;ContactForm&lt;/code&gt;, and template that should be used, &lt;code&gt;contact.html&lt;/code&gt;. Next, we have to set &lt;code&gt;get_success_url&lt;/code&gt;, which takes the URL name of our success form from the &lt;code&gt;sendemail/urls.py&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;And then we come to &lt;code&gt;form_valid,&lt;/code&gt; meaning if the form is successfully submitted, what should happen next? Django provides excellent support for &lt;a href="https://docs.djangoproject.com/en/5.0/ref/forms/validation/" rel="noopener noreferrer"&gt;form and field validation&lt;/a&gt;, so the main task for us, as developers, is to ensure each field is cleaned, layout the format of the message we want to receive, and then use &lt;code&gt;send_email&lt;/code&gt; to send the subject, message, from email, and recipient list. It is worth reading the Django docs on forms and validation for a fuller picture of all that happens behind the scenes, but suffice it to say that forms are an incredibly deep and difficult area of web development that Django handles elegantly for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create templates
&lt;/h2&gt;

&lt;p&gt;The final step is to create the templates for our contact and success pages. I like to make a project-level &lt;code&gt;templates&lt;/code&gt; folder and put all of my templates in there. So, create a new directory called &lt;code&gt;templates&lt;/code&gt; in the project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ mkdir templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, update our &lt;code&gt;settings.py&lt;/code&gt; file to tell Django to look in this directory for templates. Update the &lt;code&gt;DIRS&lt;/code&gt; settings within &lt;code&gt;TEMPLATES&lt;/code&gt; with a one-line change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DIRS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new file called &lt;code&gt;templates/contact.html&lt;/code&gt; and update it with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/contact.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Contact Us&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {% csrf_token %}
  {{ form }}
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-actions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Send first email
&lt;/h2&gt;

&lt;p&gt;Make sure the server is running with &lt;code&gt;python manage.py runserver&lt;/code&gt; and load &lt;a href="http://127.0.0.1:8000/contact/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/email/&lt;/a&gt; in your web browser, fill out the form, and click the Send button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ube05jqhztj9cz7zofm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ube05jqhztj9cz7zofm.png" alt="Contact Page" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the email goes through, you will be redirected to the &lt;a href="http://127.0.0.1:8000/success/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/success/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1engk7ccowpbuz2ni2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1engk7ccowpbuz2ni2m.png" alt="Success Page" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in your console/terminal, you can see that the email was sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 26, 2024 - 15:49:19
Django version 5.0.1, using settings 'django_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Received contact form submission
From: will@learndjango.com
To: will@learndjango.com
Date: Fri, 26 Jan 2024 15:49:28 -0000
Message-ID: &amp;lt;170628416888.90389.17657223448384564388@1.0.0.127.in-addr.arpa&amp;gt;


            Received message below from will@email.com, Hello
            ________________________


            Does this work?

-------------------------------------------------------------------------------
[26/Jan/2024 15:49:28] "POST /contact/ HTTP/1.1" 302 0
[26/Jan/2024 15:49:28] "GET /success/ HTTP/1.1" 200 43
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Production Email Service
&lt;/h2&gt;

&lt;p&gt;To send actual emails, we first must switch the &lt;code&gt;EMAIL_BACKEND&lt;/code&gt; from &lt;code&gt;console&lt;/code&gt; to &lt;code&gt;SMTP.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;will@learndjango.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;NOTIFY_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;will@learndjango.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.core.mail.backends.smtp.EmailBackend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the managed email services like &lt;a href="https://sendgrid.com/" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt;, &lt;a href="https://www.mailgun.com/" rel="noopener noreferrer"&gt;mailgun&lt;/a&gt;, or &lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;SES&lt;/a&gt; have the option to send emails via SMTP.&lt;/p&gt;

&lt;p&gt;When you sign up for an account, you will be assigned an email host, username, and password. The update to &lt;code&gt;settings.py&lt;/code&gt; is nearly identical and looks like the one below, which uses SendGrid as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;will@learndjango.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;NOTIFY_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;will@learndjango.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.core.mail.backends.smtp.EmailBackend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;smtp.sendgrid.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_HOST_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apikey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_HOST_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;sendgrid_password&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_USE_TLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more advanced usage, &lt;a href="https://github.com/anymail/django-anymail" rel="noopener noreferrer"&gt;django-anymail&lt;/a&gt; is a popular package that makes it much easier to switch between email providers.&lt;/p&gt;

&lt;p&gt;It is also worth noting that your email apikey and password &lt;strong&gt;should be kept secret&lt;/strong&gt; and stored with environment variables, not in the source code and searchable via Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With this configuration in place, you can send real emails from your Django app. Often, this is used when a user signs up for the first time, needs to reset a password, and so on. All these steps, including implementing environment variables and customizing emails are covered in the &lt;a href="https://learndjango.com/books/" rel="noopener noreferrer"&gt;Django for Beginners&lt;/a&gt; book.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>email</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Django Stripe Tutorial</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Sat, 06 Jan 2024 02:02:20 +0000</pubDate>
      <link>https://forem.com/learndjango/django-stripe-tutorial-3kco</link>
      <guid>https://forem.com/learndjango/django-stripe-tutorial-3kco</guid>
      <description>&lt;p&gt;In this tutorial, we will create a Stripe-powered Django website to handle purchases. The final site will have a homepage for orders, a success page when an order goes through, and a cancel page if the order fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;To start, open up your terminal and create a new Python virtual environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Windows
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $

# macOS
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install &lt;code&gt;Django&lt;/code&gt;, create a new project called &lt;code&gt;django_project&lt;/code&gt;, and run &lt;code&gt;migrate&lt;/code&gt; to initialize the new SQLite local database. Execute the &lt;code&gt;runserver&lt;/code&gt; command to start the local web server and confirm everything works properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ python -m pip install django~=5.0.0
(.venv) $ django-admin startproject django_project .
(.venv) $ python manage.py migrate
(.venv) $ python manage.py runserver 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your web browser, navigate to &lt;code&gt;127.0.0.1:8000&lt;/code&gt; to see the familiar Django welcome page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiysiic8qxt2b816nl87z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiysiic8qxt2b816nl87z.png" alt="Django welcome page" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Django Configuration
&lt;/h2&gt;

&lt;p&gt;We will create a dedicated &lt;code&gt;products&lt;/code&gt; app for our logic now using the &lt;code&gt;startapp&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ python manage.py startapp products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to register the app immediately in our &lt;code&gt;settings.py&lt;/code&gt; file so that Django knows to look for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start by creating the URL paths for the three pages: home, success, and cancel. We could do this in an app-level file like &lt;code&gt;products/urls.py&lt;/code&gt;, but for simplicity, we'll place them in the project-level &lt;code&gt;django_project/urls.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up are our three views that display a template file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And lastly, the template files. Create a project-level directory called &lt;code&gt;templates&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ mkdir templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, update &lt;code&gt;django_project/settings.py&lt;/code&gt; so that Django's template loader knows to look for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BACKEND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.template.backends.django.DjangoTemplates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DIRS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create three new template files--&lt;code&gt;home.html&lt;/code&gt;, &lt;code&gt;success.html&lt;/code&gt;, and &lt;code&gt;cancel.html&lt;/code&gt;--within the &lt;code&gt;templates&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/home.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Homepage&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- templates/success.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Success page&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- templates/cancel.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Cancel page&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Make sure &lt;code&gt;runserver&lt;/code&gt; is running, and then confirm that all three webpages exist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc43tbf16rx7cl6b1m9yv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc43tbf16rx7cl6b1m9yv.png" alt="Home page" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju3ovac80i32w8tvv71s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju3ovac80i32w8tvv71s.png" alt="Success page" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqi0mcke826uw4m4p67h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqi0mcke826uw4m4p67h.png" alt="Cancel page" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Stripe
&lt;/h2&gt;

&lt;p&gt;Log in to your existing Stripe account or &lt;a href="https://dashboard.stripe.com/register" rel="noopener noreferrer"&gt;register&lt;/a&gt; for a free one. If you only want to use test mode, where we can mock transactions, there is no need to complete the application with bank details. Once registered, head over to the &lt;a href="https://dashboard.stripe.com/test/dashboard" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnzjuzwst5b3k0xeyr16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnzjuzwst5b3k0xeyr16.png" alt="Stripe Dashboard Page" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the "Developers" nav link in the upper right-hand corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftctqqhx9v7vq26jy7s2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftctqqhx9v7vq26jy7s2k.png" alt="Stripe Developers page" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to activate "Test mode" and then click on the nav link for &lt;a href="https://dashboard.stripe.com/test/apikeys" rel="noopener noreferrer"&gt;API keys&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6pbb68mzb6e9fzbomqky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6pbb68mzb6e9fzbomqky.png" alt="Stripe API Keys page" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stripe provides a "Publishable key" and a "Secret key" that work together to process payment transactions. The publishable (or public) key is used to initiate a transaction and is visible to the client; the secret key is used on our server and should be kept confidential. &lt;/p&gt;

&lt;p&gt;We will hardcode both for this tutorial into the bottom of our &lt;code&gt;django_project/settings.py&lt;/code&gt; file. The live API keys should be stored in environment variables in production for proper security.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project/settings.py
&lt;/span&gt;&lt;span class="n"&gt;STRIPE_PUBLIC_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your_test_public_api_key&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;STRIPE_SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your_test_secret_api_key&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need a product to sell. On the lefthand sidebar, click the link for "More +" and then "Product catalog," which takes us to the &lt;a href="https://dashboard.stripe.com/test/products?active=true" rel="noopener noreferrer"&gt;Products&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnduuk3bberqe8bjmo8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnduuk3bberqe8bjmo8a.png" alt="Stripe Products Navbar Link" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "+ Add product" button in the upper right-hand corner to create a product to sell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs025jh5sck0w5vtbjomw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs025jh5sck0w5vtbjomw.png" alt="Stripe Products Page" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The required fields are name, one-off or recurring, and the amount. We are processing a one-off payment here. Click the "Add Product" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8je7ezhl3bqo0irw5xlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8je7ezhl3bqo0irw5xlj.png" alt="Stripe Add a Product Page" width="800" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are redirected to the main "Product catalog" page, where our new product is now visible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1x614kshca7wrnetpof.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1x614kshca7wrnetpof.png" alt="Stripe Product Catalog Page" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the new product to open up its page. Under the Pricing section, note the "API ID" which we will use shortly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yzeets98hs8jid1g5ky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yzeets98hs8jid1g5ky.png" alt="Stripe Product Page" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have a product to sell, we will focus on the Django app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe Hosted Page
&lt;/h2&gt;

&lt;p&gt;If you look at the &lt;a href="https://stripe.com/docs/checkout/quickstart" rel="noopener noreferrer"&gt;Stripe docs&lt;/a&gt;, the Python example provided is for a Flask Stripe-hosted page. However, we can adapt this for our Django app. At a high level, we need to add an HTML form to the &lt;code&gt;home.html&lt;/code&gt; template and then update our &lt;code&gt;views.py&lt;/code&gt; file so that when a POST request is sent to Stripe, our server-side Stripe SDK and Stripe Private key are used to generate a new Checkout session. The user is redirected to this unique session and then, if payment is successful, redirected to our &lt;code&gt;success.html&lt;/code&gt; page. Otherwise, they will be sent to the &lt;code&gt;cancel.html&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;Let's start by installing the Python library for Stripe, which is &lt;a href="https://github.com/stripe/stripe-python" rel="noopener noreferrer"&gt;available on Github&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.venv) $ python -m pip install stripe==7.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;home.html&lt;/code&gt; file, add a basic form using the POST method and Django's &lt;a href="https://docs.djangoproject.com/en/5.0/howto/csrf/" rel="noopener noreferrer"&gt;CSRF protection&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/home.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Homepage&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {% csrf_token %}
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Purchase!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the magic happens in our &lt;code&gt;views.py&lt;/code&gt; file. The updated code is below, and we'll work through the changes line-by-line. But before getting into specifics, it's essential to understand that at a high level, we are loading in the Stripe Private Key from our &lt;code&gt;settings.py&lt;/code&gt; file and using it to create a new Checkout session. The user will be redirected to a custom URL for the Stripe-hosted checkout page and then, after submitting payment, either sent to a success or cancel page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STRIPE_SECRET_KEY&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;checkout_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;line_items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;price_API_ID_HERE&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# enter yours here!!!
&lt;/span&gt;                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;success_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_absolute_uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;cancel_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_absolute_uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkout_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;303&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top, we've imported &lt;code&gt;redirect&lt;/code&gt;, &lt;code&gt;settings&lt;/code&gt;, &lt;code&gt;reverse&lt;/code&gt;, and &lt;code&gt;stripe&lt;/code&gt;. The first step in our &lt;code&gt;home&lt;/code&gt; function-based view is to set the Stripe &lt;code&gt;api_key&lt;/code&gt; to the Stripe Private Key for our account; it is stored in our &lt;code&gt;settings.py&lt;/code&gt; file for security and &lt;em&gt;not&lt;/em&gt; visible to the user client-side. Next, if a &lt;code&gt;POST&lt;/code&gt; request is made a new &lt;code&gt;checkout_session&lt;/code&gt; is created and requires, at a minimum, the following attributes: &lt;code&gt;line_items&lt;/code&gt;, &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;success_url&lt;/code&gt;, and &lt;code&gt;cancel_url&lt;/code&gt;. Other &lt;a href="https://stripe.com/docs/api/checkout/sessions/object" rel="noopener noreferrer"&gt;attributes are available&lt;/a&gt; but are not necessary at this stage.&lt;/p&gt;

&lt;p&gt;We &lt;a href="https://stripe.com/docs/checkout/quickstart#mode" rel="noopener noreferrer"&gt;define a product to sell&lt;/a&gt; with the &lt;code&gt;line_items&lt;/code&gt; attribute, using the exact Price ID for our product set on the Stripe website. Later on we might want to have the option of storing multiple IDs in our database to load in, but hardcoding it is fine for example purposes. We also specify the &lt;code&gt;quantity&lt;/code&gt; of &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we &lt;a href="https://stripe.com/docs/checkout/quickstart#mode" rel="noopener noreferrer"&gt;set a mode&lt;/a&gt; to "payment" for one-time purchases, but we could also do "subscription" for subscriptions or "setup" to collect customer payment details to reuse later.&lt;/p&gt;

&lt;p&gt;After the user places an order, they are sent to either a &lt;code&gt;success_url&lt;/code&gt; or a &lt;code&gt;cancel_url&lt;/code&gt;. Django's &lt;a href="https://docs.djangoproject.com/en/5.0/ref/urlresolvers/#reverse" rel="noopener noreferrer"&gt;reverse()&lt;/a&gt; function is used alongside the URL name of each. We also take advantage of Django's [build_absolute_uri] method to send an absolute URL to Stripe.&lt;/p&gt;

&lt;p&gt;The final bit is &lt;a href="https://stripe.com/docs/checkout/quickstart#redirect" rel="noopener noreferrer"&gt;redirecting to Checkout&lt;/a&gt;. After our POST request is sent to Stripe, authenticated, and a new Checkout session is created, Stripe's API will send us a unique &lt;code&gt;checkout_session.url&lt;/code&gt; that the user is redirected to.&lt;/p&gt;

&lt;p&gt;The full Stripe Checkout flow can take a while to sink in, but that's all the code we need! &lt;/p&gt;

&lt;p&gt;Ensure the server is running, and go to the homepage at &lt;code&gt;127.0.0.1:8000&lt;/code&gt; and click on the "Purchase" button. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2s65vxva73sqi9oha52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2s65vxva73sqi9oha52.png" alt="Purchase Button Page" width="800" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll be redirected to a unique Stripe Checkout session for the product. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9s0ac3t72haxtk4hrs33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9s0ac3t72haxtk4hrs33.png" alt="Stripe Checkout Session Page" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in the form. For the credit card information, use the number &lt;code&gt;4242 4242 4242 4242&lt;/code&gt;, any date in the future for expiration, and any three digits for the CVC. Then click the "Pay" button at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuyqx2d7tgn0jazk8oepx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuyqx2d7tgn0jazk8oepx.png" alt="Stripe Checkout Session Page Filled Out" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After successful payment, you will be redirected to the success page at &lt;code&gt;http://127.0.0.1:8000/success/&lt;/code&gt;. You can view the confirmation of the payment on your Stripe dashboard. The &lt;a href="https://dashboard.stripe.com/test/payments" rel="noopener noreferrer"&gt;Payments&lt;/a&gt; section, available from the left sidebar, provides further payment details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekprbxkpntdmxein4g2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekprbxkpntdmxein4g2t.png" alt="Stripe Payments Page" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to &lt;a href="https://stripe.com/docs/checkout/quickstart#customize-toggle" rel="noopener noreferrer"&gt;customize the Checkout page&lt;/a&gt;, you can pass in additional attributes to the Checkout session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;For more free tutorials and premium courses on Django, check out &lt;a href="https://learndjango.com" rel="noopener noreferrer"&gt;LearnDjango.com&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Django Best Practices: Security</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Tue, 22 Sep 2020 17:18:17 +0000</pubDate>
      <link>https://forem.com/learndjango/django-best-practices-security-4fpp</link>
      <guid>https://forem.com/learndjango/django-best-practices-security-4fpp</guid>
      <description>&lt;p&gt;Django is a mature, battle-tested web framework with a well deserved reputation for security over the past 15+ years.However the internet remains a dangerous place and web security is an evolving field. Like most web frameworks, Django defaults to local development settings when a new project is created. The onus is on the developer to effectively manage both local defaults &lt;strong&gt;and&lt;/strong&gt; customized production settings.&lt;/p&gt;

&lt;p&gt;Django comes with an excellent &lt;a href="https://docs.djangoproject.com/en/dev/howto/deployment/checklist/" rel="noopener noreferrer"&gt;deployment checklist&lt;/a&gt; that can be run on the production version of your website before deployment. As well as copious notes on &lt;a href="https://docs.djangoproject.com/en/dev/topics/security/" rel="noopener noreferrer"&gt;security in the official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This tutorial covers Django security best practices starting with the most important and working our way down the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django Version
&lt;/h2&gt;

&lt;p&gt;The number one security recommendation is to always be on the latest version of Django. Django has a new major release every 9 months or so (2.2, 3.0, 3.1, etc) and a minor release with security/bug fixes almost monthly (3.1.1, 3.1.2, etc). Take the time to update regularly to the latest version--there is an official guide in the documentation on &lt;a href="https://docs.djangoproject.com/en/dev/howto/upgrade-version/" rel="noopener noreferrer"&gt;upgrading to a newer version&lt;/a&gt;--run your test suite, and carry on with your Django project. &lt;/p&gt;

&lt;p&gt;Note that writing comprehensive tests and implementing CI so that the tests are run on each commit is a topic for another tutorial or full-length book.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;There is a fundmental tension between settings intended for local development and production. Local development prizes speed, robust bug reports, and access to all features. Production requires locking down the website and minimizing access as much as possible. By default, new Django projects created with the &lt;code&gt;startproject&lt;/code&gt; command have local development settings. The developer is responsible for updating them to production settings.&lt;/p&gt;

&lt;p&gt;These days, the standard way to switch between local and production settings is with &lt;a href="https://en.wikipedia.org/wiki/Environment_variable" rel="noopener noreferrer"&gt;environment variables&lt;/a&gt;. Rather than having multiple &lt;code&gt;settings.py&lt;/code&gt; files, a single &lt;code&gt;settings.py&lt;/code&gt; file can be used with variables loaded in depending upon the environment: local or production.&lt;/p&gt;

&lt;p&gt;There are multiple ways to implement environment variables and various third-party packages that can help. Personally, I like using &lt;a href="https://github.com/sloria/environs" rel="noopener noreferrer"&gt;environs&lt;/a&gt;, which has a Django-specific option that installs a number of additional packages that help with configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  DEBUG
&lt;/h2&gt;

&lt;p&gt;Near the top of any &lt;code&gt;settings.py&lt;/code&gt; file is the configuration for &lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DEBUG" rel="noopener noreferrer"&gt;DEBUG&lt;/a&gt;. By default, it is set to &lt;code&gt;True&lt;/code&gt; which generates, among other things, a rich and detailed error page that includes a traceback and lots of metadata including all currently defined Django settings. That is a roadmap to debugging locally, but also a guide for any hacker who wants to compromise your website if released in production. Therefore, make sure that in production &lt;code&gt;DEBUG&lt;/code&gt; is set to &lt;code&gt;False&lt;/code&gt;! &lt;/p&gt;

&lt;h2&gt;
  
  
  SECRET_KEY
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECRET_KEY" rel="noopener noreferrer"&gt;SECRET_KEY&lt;/a&gt; is a randomly generated string used for &lt;a href="https://docs.djangoproject.com/en/dev/topics/signing/" rel="noopener noreferrer"&gt;cryptographic signing&lt;/a&gt; created whenever the &lt;code&gt;startproject&lt;/code&gt; command is run. It is very important that &lt;code&gt;SECRET_KEY&lt;/code&gt; actually be kept, well, secret.&lt;/p&gt;

&lt;p&gt;All it takes is one Git commit and your &lt;code&gt;SECRET_KEY&lt;/code&gt; is visible to anyone with access to your source code. In practice, most developers develop a working prototype of a website before adding environment variables and production configurations. Therefore, before the first deployment, take the time to generate a new &lt;code&gt;SECRET_KEY&lt;/code&gt;. One approach is to use Python's built-in &lt;a href="https://docs.python.org/3/library/secrets.html" rel="noopener noreferrer"&gt;secrets&lt;/a&gt; module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python -c 'import secrets; print(secrets.token_urlsafe(38))'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parameter &lt;code&gt;token_urlsafe&lt;/code&gt; returns the number of bytes in a URL-safe text string. With Base64 encoding on average each byte has 1.3 characters. So using &lt;code&gt;38&lt;/code&gt; results in 51 characters in this case. The important thing is that your &lt;code&gt;SECRET_KEY&lt;/code&gt; has at least 50 characters. Each time you run the command, a new value is outputted.&lt;/p&gt;

&lt;h2&gt;
  
  
  ALLOWED_HOSTS
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts" rel="noopener noreferrer"&gt;ALLOWED_HOSTS&lt;/a&gt; setting lists which hosts/domain names your Django site can serve. By default, it is set to the empty list, &lt;code&gt;[]&lt;/code&gt;, aka any host or domain has access to your site. This needs to be changed in production to avoid HTTP Header attacks.&lt;/p&gt;

&lt;p&gt;For local development &lt;code&gt;localhost:8000&lt;/code&gt; and &lt;code&gt;127.0.0.1:8000&lt;/code&gt; are commonly used in Django, therefore both should be explicitly added to the setting. And once you have a custom URL for your production location--for example, if you're using Heroku it will contain &lt;code&gt;.herokuapp.com&lt;/code&gt;, this should be added as well.&lt;/p&gt;

&lt;p&gt;A common &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; setting for a Heroku app is therefore the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;ALLOWED_HOSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.herokuapp.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  HTTPS/SSL
&lt;/h2&gt;

&lt;p&gt;These days there is no reason &lt;strong&gt;not&lt;/strong&gt; to deploy your entire website behind HTTPS. Otherwise a hacker could sniff authentication credentials, API keys, or really any information transferred between the client and server. There are a number of settings to enable full SSL support in a Django project. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECURE_SSL_REDIRECT" rel="noopener noreferrer"&gt;SECURE_SSL_REDIRECT&lt;/a&gt; automatically redirects requests over HTTP to HTTPS. It should be set to &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" rel="noopener noreferrer"&gt;HTTP Strict Transport Security (HSTS)&lt;/a&gt; is a security policy that lets our server enforce that web browsers should only interact via HTTPS by adding a &lt;a href="https://docs.djangoproject.com/en/3.1/ref/middleware/#http-strict-transport-security" rel="noopener noreferrer"&gt;Strict-Transport-Security header&lt;/a&gt;. There are three implicit HSTS configurations in a &lt;code&gt;settings.py&lt;/code&gt; file that need to be updated for production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECURE_HSTS_SECONDS" rel="noopener noreferrer"&gt;SECURE_HSTS_SECONDS&lt;/a&gt; = &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains" rel="noopener noreferrer"&gt;SECURE_HSTS_INCLUDE_SUBDOMAINS&lt;/a&gt; = &lt;code&gt;False&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload" rel="noopener noreferrer"&gt;SECURE_HSTS_PRELOAD&lt;/a&gt; = &lt;code&gt;False&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;SECURE_HSTS_SECONDS&lt;/code&gt; setting is set to &lt;code&gt;0&lt;/code&gt; by default but the greater the better for security purposes. A good default is to set it to one month, 2,592,000 seconds, instead. &lt;code&gt;SECURE_HSTS_INCLUDE_SUBDOMAINS&lt;/code&gt; forces subdomains to also exclusively use SSL so it should be set to &lt;code&gt;True&lt;/code&gt; in production. And &lt;code&gt;SECURE_HSTS_PRELOAD&lt;/code&gt; only has an effect when there is a non-zero value for &lt;code&gt;SECURE_HSTS_SECONDS&lt;/code&gt;, but since we just set one, it will need to be set to &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Media Files
&lt;/h2&gt;

&lt;p&gt;Most Django websites will contain files that are static and unchanging such as CSS, JavaScript, images, and so on. There are broadly referred to as &lt;code&gt;Static&lt;/code&gt; files. For certain websites, user-uploaded content is a key feature, such as an image sharing website similar to Instagram. Django refers to user-uploaded content as &lt;code&gt;Media&lt;/code&gt; and additional precautions must be made.&lt;/p&gt;

&lt;p&gt;In general, you can't trust your users. Especially when they have the ability to upload something to your website. Therefore for websites with media files, hosting all static content on a cloud service or CDN, like Amazon's S3, is necessary. The third party package &lt;a href="https://django-storages.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;django-storages&lt;/a&gt; is a popular way to configure this for your website and alleviate media security concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Admin
&lt;/h2&gt;

&lt;p&gt;The Django admin app is a powerful built-in feature. However, since every Django project has it located at &lt;code&gt;/admin&lt;/code&gt; by default, it is easy for a hacker to try to force their way into any Django website at this URL. An easy way to secure your admin is to simply change the URL for the admin. In the example below it has been changed to &lt;code&gt;anything-but-admin&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/urls.py
&lt;/span&gt;&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anything-but-admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For additional tips on hardening your admin, see the article &lt;a href="https://opensource.com/article/18/1/10-tips-making-django-admin-more-secure" rel="noopener noreferrer"&gt;10 Tips for Making the Django Admin More Secure&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are many more security improvements that can be added to a Django project but this hits the highlights. Make sure to run the Django deployment checklist on your production settings to be sure you have made all the requisite changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python manage.py check --deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For further help on security, the book &lt;a href="https://djangoforprofessionals.com" rel="noopener noreferrer"&gt;Django for Professionals&lt;/a&gt; walks through building out a production-ready website with proper security features, performance tips, Docker usage, and more. &lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>"Django Hosting Options"</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Fri, 04 Sep 2020 22:02:11 +0000</pubDate>
      <link>https://forem.com/learndjango/django-hosting-options-5gne</link>
      <guid>https://forem.com/learndjango/django-hosting-options-5gne</guid>
      <description>&lt;p&gt;&lt;a href="https://www.djangoproject.com/" rel="noopener noreferrer"&gt;Django&lt;/a&gt; websites can be deployed on any number of hosting providers. The first choice is deciding whether to use a Platform-as-a-service (PaaS) option or a virtual private server (VPS). A PaaS is an easier albeit more expensive option that can handle many deployment issues with minimal configuration. A VPS is less expensive and provides total control but requires more knowledge and effort to setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform-as-a-Service Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; is the original PaaS. It has a free tier for small projects and progressive pricing as a site grows in size. There is also a robust &lt;a href="https://elements.heroku.com/addons/#data-stores" rel="noopener noreferrer"&gt;add-on ecosystem&lt;/a&gt; for additional services like database hosting, caching, logging, and more. Check out the &lt;a href="https://devcenter.heroku.com/articles/deploying-python" rel="noopener noreferrer"&gt;Heroku Django Getting Started Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.pythonanywhere.com/details/django_hosting" rel="noopener noreferrer"&gt;PythonAnywhere&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.pythonanywhere.com/details/django_hosting" rel="noopener noreferrer"&gt;PythonAnywhere&lt;/a&gt; is another PaaS option that specializes in online Python environments. It also features a free tier and upgraded plans that start at $5/month. &lt;a href="https://help.pythonanywhere.com/pages/DeployExistingDjangoProject" rel="noopener noreferrer"&gt;Deploying an existing Django app is straightforward&lt;/a&gt; and also demonstrated as part of the excellent &lt;a href="https://tutorial.djangogirls.org/en/deploy/" rel="noopener noreferrer"&gt;DjangoGirls Tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.divio.com" rel="noopener noreferrer"&gt;Divio&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.divio.com" rel="noopener noreferrer"&gt;Divio&lt;/a&gt; is a Django-specific platform that comes with pre-configured projects including Django CMS and Wagtail.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://azure.microsoft.com/en-us/develop/python/" rel="noopener noreferrer"&gt;Microsoft Azure&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A managed PaaS option that includes a nice &lt;a href="https://www.youtube.com/playlist?list=PLLasX02E8BPC8UJKPe1Q43MZbS4OmaCsM" rel="noopener noreferrer"&gt;video tutorial series&lt;/a&gt; with my &lt;a href="https://djangochat.com" rel="noopener noreferrer"&gt;DjangoChat podcast&lt;/a&gt; co-host Carlton Gibson.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://render.com/docs/deploy-django" rel="noopener noreferrer"&gt;Render&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://render.com/docs/deploy-django" rel="noopener noreferrer"&gt;Render&lt;/a&gt; is a new but quite popular cloud option that allows for deploys directly from Github or Gitlab, similar to Netlify. Has options for hosted databases, cron jobs, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://cloud.google.com/python/django/" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Google provides multiple PaaS options starting with &lt;a href="https://cloud.google.com/python/django/appengine" rel="noopener noreferrer"&gt;App Engine&lt;/a&gt; which is where to start but continuing with multiple more advanced options including Kubernetes and Compute Engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://dokku.viewdocs.io/dokku/" rel="noopener noreferrer"&gt;Dokku&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A lightweight, &lt;a href="https://github.com/dokku/dokku" rel="noopener noreferrer"&gt;open source&lt;/a&gt; Docker-powered PaaS that promises to be cheaper than alternatives. There are some &lt;a href="https://www.stavros.io/posts/deploy-django-dokku/" rel="noopener noreferrer"&gt;good tutorials&lt;/a&gt; on using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual Private Servers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://m.do.co/c/d1f83a0a642d" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;If you're comfortable doing basic server configuration, for as little as $5/month you can host your entire Django project on &lt;a href="https://m.do.co/c/d1f83a0a642d" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt;. They have &lt;a href="https://www.digitalocean.com/community/tags/django?type=tutorials" rel="noopener noreferrer"&gt;fantastic documentation&lt;/a&gt; and &lt;a href="https://www.digitalocean.com/products/one-click-apps/django/" rel="noopener noreferrer"&gt;one-click installs&lt;/a&gt; for common setups like Ubuntu.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.linode.com/?r=70d00cc48f300a318474ba61cb7e7663b6e1531b" rel="noopener noreferrer"&gt;Linode&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.linode.com/?r=70d00cc48f300a318474ba61cb7e7663b6e1531b" rel="noopener noreferrer"&gt;Linode&lt;/a&gt; is another very popular VPS provider also starting at $5/month that has fantastic customer support.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://aws.amazon.com/getting-started/hands-on/deploy-python-application/" rel="noopener noreferrer"&gt;AWS Lightsail&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Amazon's AWS is famously complex and Lightsail bills itself as the "easiest way to get started" by offering virtual servers, storage, databases, and networking in a monthly plan. Appropriate for small applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-django.html" rel="noopener noreferrer"&gt;AWS Elastic Beanstalk&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Elastic Beanstalk is an orchestration tool that provides a layer of abstraction over AWS's 100+ services. It does all the work to create an EC2, install apps, provides a load balancer, and so on. Lightsail is designed for prototypes while Elastic Beanstalk is more suited to &lt;strong&gt;scaling&lt;/strong&gt; an existing application. It is likely more expensive than &lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;raw EC2&lt;/a&gt;, which is just a remote Linux machine.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Django NameError: name 'os' is not defined</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Wed, 22 Jul 2020 22:29:35 +0000</pubDate>
      <link>https://forem.com/learndjango/django-nameerror-name-os-is-not-defined-21o3</link>
      <guid>https://forem.com/learndjango/django-nameerror-name-os-is-not-defined-21o3</guid>
      <description>&lt;p&gt;If you've started a new Django 3.1+ project and are using older tutorials or guides, it's likely to come across the following error on your command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NameError: name 'os' is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting with Django 3.1, the &lt;code&gt;startproject&lt;/code&gt; command generates a &lt;code&gt;settings.py&lt;/code&gt; file that imports &lt;a href="https://docs.python.org/3/library/pathlib.html" rel="noopener noreferrer"&gt;pathlib&lt;/a&gt; rather than &lt;a href="https://docs.python.org/3/library/os.html" rel="noopener noreferrer"&gt;os&lt;/a&gt; on the top line.&lt;/p&gt;

&lt;p&gt;The quick fix is to &lt;code&gt;import os&lt;/code&gt; at the top of your &lt;code&gt;settings.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The better fix is learn more about how &lt;code&gt;pathlib&lt;/code&gt; works and update your &lt;code&gt;BASE_DIR&lt;/code&gt;, &lt;code&gt;DATABASES&lt;/code&gt;, &lt;code&gt;STATICFILES_DIRS&lt;/code&gt;, and other files to use the newer, modern approach.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What is Django (Python)?</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Mon, 20 Jul 2020 14:15:10 +0000</pubDate>
      <link>https://forem.com/learndjango/what-is-django-python-314a</link>
      <guid>https://forem.com/learndjango/what-is-django-python-314a</guid>
      <description>&lt;p&gt;&lt;a href="https://djangoproject.com" rel="noopener noreferrer"&gt;Django&lt;/a&gt; is an open-source web framework written in the &lt;a href="https://www.python.org" rel="noopener noreferrer"&gt;Python&lt;/a&gt; programming language. Named after the jazz guitarist &lt;a href="https://en.wikipedia.org/wiki/Django_Reinhardt" rel="noopener noreferrer"&gt;Django Reinhardt&lt;/a&gt;, it is used by some of the largest websites in the world including Instagram, Mozilla, and NASA, but also lightweight enough to be a popular choice for weekend side projects and startups. Its "batteries-included" approach means a powerful website can be generated quickly in the hands of a skilled developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Framework
&lt;/h2&gt;

&lt;p&gt;A "web framework" is software that standardizes and abstracts away common difficulties and redundancies involved in making a website. For example, most websites need to connect to a database, deploy to a server, handle URL routing, security, user registration, generate templates, and so on. In the early days, programmers had to do all of this from scratch but they quickly recognized the commonalities and started creating web frameworks.&lt;/p&gt;

&lt;p&gt;These days, it is rare to write a website from scratch. Web frameworks exist for every modern programming languages including &lt;a href="https://rubyonrails.org" rel="noopener noreferrer"&gt;Rails for Ruby&lt;/a&gt;, &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;Express&lt;/a&gt; for JavaScript, and Django or Flask for Python.&lt;/p&gt;

&lt;p&gt;Beyond the underlying programming language, the major difference between competing web frameworks is one of approach: Ruby on Rails and Django both adopt a "batteries-included" frameworks approach whereas Flask and Express are minimal microframeworks. You can think of it as the difference between being given a bag of Legos or a pre-built truck made out of Legos. Django and Rails are faster to start using and come with many built-in guardrails to prevent bad practices. Flask and Express require more customization upfront but allow for total control. See &lt;a href="https://learndjango.com/tutorials/flask-vs-django" rel="noopener noreferrer"&gt;Django vs Flask&lt;/a&gt; for a detailed comparison of the two most popular Python web frameworks and the pros/cons of each approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;Python is one of the most popular programming languages in the world because it is friendly to newcomers, extremely powerful, and has a robust ecosystem of libraries like Django that provide additional functionality. Python recently replaced Java as the &lt;a href="http://cacm.acm.org/blogs/blog-cacm/176450-python-is-now-the-most-popular-introductory-teaching-language-at-top-us-universities/fulltext" rel="noopener noreferrer"&gt;most popular programming language at top U.S. universities&lt;/a&gt; and is growing rapidly among professional programmers as well, as evidenced by &lt;a href="https://stackoverflow.blog/2017/09/06/incredible-growth-python/" rel="noopener noreferrer"&gt;its growth on StackOverflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Python emphasizes readability and developer friendliness, as evidenced by its "Hello, World" syntax. Here it is in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here it is in Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorldApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if you know nothing about either programming language, it is clear at a glance that Python is the easier of the two to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Django was initially developed by web programmers at the &lt;em&gt;Lawrence Journal-World&lt;/em&gt; newspaper, specifically Adrian Holovaty, Simon Willison, and Jacob Kaplan-Moss. It was first released to the public as an open source package in 2005 and is currently maintained by the non-profit &lt;a href="https://www.djangoproject.com/foundation/" rel="noopener noreferrer"&gt;Django Software Foundation&lt;/a&gt;. The source code is &lt;a href="https://github.com/django/django" rel="noopener noreferrer"&gt;freely available online&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;p&gt;Django adopts a "batteries-included" approach similar to Python and comes with a number of built-in features including an extensible authentication system, robust admin app, lightweight testing web server, and support for multiple databases including PostgreSQL, MySQL, MariaDB, Oracle, and SQLite. It is known for its leading security best practices and comes with comprehensive &lt;a href="https://docs.djangoproject.com/en/dev/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, available either online or as a PDF/ePUB for offline consumption.&lt;/p&gt;

&lt;p&gt;As a mature project, Django rarely makes breaking changes and has a clear deprecation schedule for any updates. A major new version is released every &lt;a href="https://www.djangoproject.com/download/" rel="noopener noreferrer"&gt;nine months or so&lt;/a&gt; with monthly patch releases for security and bug fixes.&lt;/p&gt;

&lt;p&gt;There is also a vibrant ecosystem of third-party applications--visible on the &lt;a href="https://djangopackages.org/" rel="noopener noreferrer"&gt;Django Packages site&lt;/a&gt;--which provide additional functionality. Over time, the most popular packages are often rolled into Django itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community
&lt;/h2&gt;

&lt;p&gt;A common saying among Django developers is "Come for the framework, stay for the community." It is known for being a welcoming and encouraging community, which is not always the case in technology. There are annual DjangoCon conferences hosted in the U.S, Europe, Australia, and Africa, as well as meetups in many major cities.&lt;/p&gt;

&lt;p&gt;Questions about Django can be asked on the &lt;a href="https://forum.djangoproject.com/" rel="noopener noreferrer"&gt;official Django forum&lt;/a&gt; and newcomers are encourage to &lt;a href="https://docs.djangoproject.com/en/dev/internals/contributing/" rel="noopener noreferrer"&gt;contribute to Django&lt;/a&gt; itself.&lt;/p&gt;

&lt;p&gt;The weekly &lt;a href="https://djangochat.com/" rel="noopener noreferrer"&gt;Django Chat&lt;/a&gt; podcast features interviews with key figures in the community as well as deep dives on various core features. There is also a weekly &lt;a href="https://django-news.com/" rel="noopener noreferrer"&gt;Django News&lt;/a&gt; newsletter with updates on events, tutorials, and third-party packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you'd like to learn more about Django and try it out yourself, there is an &lt;a href="https://docs.djangoproject.com/en/dev/intro/tutorial01/" rel="noopener noreferrer"&gt;official tutorial&lt;/a&gt; as well as many more beginner friendly resources available on &lt;a href="https://learndjango.com" rel="noopener noreferrer"&gt;LearnDjango.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Django Log In with Email not Username</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Mon, 13 Jul 2020 14:06:47 +0000</pubDate>
      <link>https://forem.com/learndjango/django-log-in-with-email-not-username-ep</link>
      <guid>https://forem.com/learndjango/django-log-in-with-email-not-username-ep</guid>
      <description>&lt;p&gt;Django was first released in 2005 and since then a lot has changed in web development, notably the predominant pattern at the time of using username/email/password has been simplified to just email/password. However, due to legacy reasons around the built-in Django &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/auth/#user-model" rel="noopener noreferrer"&gt;User model&lt;/a&gt;, it can take a few extra steps to implement this change in practice.&lt;/p&gt;

&lt;p&gt;In this tutorial we'll walk through how to implement email and password only for sign up and log in on a new Django project using a custom user model and the popular &lt;a href="https://github.com/pennersr/django-allauth" rel="noopener noreferrer"&gt;django-allauth&lt;/a&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;I'll assume you know how to start and install a new Django project using Pipenv. If not, &lt;a href="https://djangoforbeginners.com/initial-setup/" rel="noopener noreferrer"&gt;please see here&lt;/a&gt; for additional help.&lt;/p&gt;

&lt;p&gt;On the command line, create a new directory for our code called &lt;code&gt;code&lt;/code&gt; (it can live anywhere on your computer but we'll use the Desktop as I'm on a Mac). Then install Django, start the virtual environment with &lt;code&gt;shell&lt;/code&gt;, and create a new project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ~/Desktop
$ mkdir code &amp;amp;&amp;amp; cd code
$ pipenv install django~=3.0.0
$ pipenv shell
(code) $ django-admin startproject config .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, we &lt;em&gt;could&lt;/em&gt; just jump into it here but I'm done, done, done with writing any more Django tutorials that don't use a custom user model upfront. So, let's configure a custom user model before running &lt;code&gt;migrate&lt;/code&gt; for the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom User Model
&lt;/h2&gt;

&lt;p&gt;I wrote about &lt;a href="https://learndjango.com/tutorials/django-custom-user-model" rel="noopener noreferrer"&gt;this in length over here&lt;/a&gt; so I'm just going to give the commands in this post.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;users&lt;/code&gt; app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ python manage.py startapp users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;settings.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# Local
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users.apps.UsersConfig&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users.CustomUser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create our &lt;code&gt;CustomUser&lt;/code&gt; model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# users/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AbstractUser&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# add additional fields in here
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new &lt;code&gt;users/forms.py&lt;/code&gt; file and then fill it with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# users/forms.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.auth.forms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserCreationForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserChangeForm&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CustomUser&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserCreationForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserCreationForm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomUser&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserChangeForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserChangeForm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomUser&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update the admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# users/admin.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.auth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_user_model&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.auth.admin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserAdmin&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.forms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CustomUserCreationForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomUserChangeForm&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CustomUser&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;add_form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomUserCreationForm&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomUserChangeForm&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomUser&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomUserAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now make a migrations file, migrate the database the first time, and create a superuser so we can access the admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ python manage.py makemigrations users
(code) $ python manage.py migrate
(code) $ python manage.py createsuperuser
(code) $ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local server is now running so you can visit both &lt;a href="http://127.0.0.1:8000" rel="noopener noreferrer"&gt;http://127.0.0.1:8000&lt;/a&gt; for the Django welcome page and also &lt;a href="http://127.0.0.1:8000/admin" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin&lt;/a&gt; to log in with our new superuser account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqnl38vnq4z06q1hhty1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqnl38vnq4z06q1hhty1q.png" alt="Django admin page" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to &lt;strong&gt;log out&lt;/strong&gt; of the admin at this point. Why? Because if you don't then &lt;a href="https://docs.djangoproject.com/en/3.0/ref/settings/#login-redirect-url" rel="noopener noreferrer"&gt;Django will automatically send&lt;/a&gt; all future URL requests to &lt;code&gt;/accounts/profile/&lt;/code&gt; and future stuff won't work.&lt;/p&gt;

&lt;p&gt;We will configure this properly later on but for now, just make sure you're logged out or stuff will break.&lt;/p&gt;

&lt;h2&gt;
  
  
  django-allauth
&lt;/h2&gt;

&lt;p&gt;Django does not come with built-in views, urls, and templates for a sign up page. So while we can roll our own a better approach--and the current default for many professionals--is to use the &lt;a href="https://github.com/pennersr/django-allauth" rel="noopener noreferrer"&gt;django-allauth&lt;/a&gt; package instead.&lt;/p&gt;

&lt;p&gt;Install it with Pipenv making sure you've used &lt;code&gt;Control+c&lt;/code&gt; to stop the local server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ pipenv install django-allauth~=0.42.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add it to our &lt;code&gt;INSTALLED_APPS&lt;/code&gt; setting and add Django's optional &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/sites/" rel="noopener noreferrer"&gt;sites framework&lt;/a&gt; which &lt;code&gt;django-allauth&lt;/code&gt; uses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sites&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
    &lt;span class="c1"&gt;# 3rd party
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth.account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth.socialaccount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
    &lt;span class="c1"&gt;# Local
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users.apps.UsersConfig&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, here's where the magic happens. &lt;code&gt;django-allauth&lt;/code&gt; has a lengthy list of &lt;a href="https://django-allauth.readthedocs.io/en/latest/configuration.html" rel="noopener noreferrer"&gt;configuration options&lt;/a&gt;. Here's what we need.&lt;/p&gt;

&lt;p&gt;Add this at the bottom of our &lt;code&gt;config/settings.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.core.mail.backends.console.EmailBackend&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;AUTHENTICATION_BACKENDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# Needed to login by username in Django admin, regardless of `allauth`
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.backends.ModelBackend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# `allauth` specific authentication methods, such as login by e-mail
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allauth.account.auth_backends.AuthenticationBackend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;SITE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;ACCOUNT_EMAIL_REQUIRED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_USERNAME_REQUIRED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_SESSION_REMEMBER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_AUTHENTICATION_METHOD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_UNIQUE_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run &lt;code&gt;migrate&lt;/code&gt; since we've updated our &lt;code&gt;INSTALLED_APPS&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  URLs
&lt;/h2&gt;

&lt;p&gt;We need to tell Django &lt;em&gt;where&lt;/em&gt; to put our fancy &lt;code&gt;django-allauth&lt;/code&gt; config so let's use the &lt;code&gt;accounts/&lt;/code&gt; path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accounts/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Default Log In and Sign Up Pages
&lt;/h2&gt;

&lt;p&gt;If you start up the local server now with &lt;code&gt;python manage.py runserver&lt;/code&gt; you can navigate to our working log in page at  &lt;a href="http://127.0.0.1:8000/accounts/login/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/accounts/login/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F60z4rtnpebk4gs5c6atj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F60z4rtnpebk4gs5c6atj.png" alt="Log in" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now take a look at the sign up page at &lt;a href="http://127.0.0.1:8000/accounts/signup/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/accounts/signup/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwcr7f7ga31irhl5o9jdg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwcr7f7ga31irhl5o9jdg.png" alt="Sign up" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates
&lt;/h2&gt;

&lt;p&gt;Time to configure our templates so things look better. &lt;code&gt;django-allauth&lt;/code&gt; uses templates in an &lt;code&gt;account&lt;/code&gt; folder so we'll override its defaults there.&lt;/p&gt;

&lt;p&gt;Create a project-level &lt;code&gt;templates&lt;/code&gt; folder and then &lt;code&gt;account&lt;/code&gt; within it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ mkdir templates
(code) $ mkdir templates/account
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now tell Django to look here so update the &lt;code&gt;TEMPLATES&lt;/code&gt; config within &lt;code&gt;settings.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DIRS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;templates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And make our log in and sign up templates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ touch templates/account/login.html
(code) $ touch templates/account/signup.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok here's the code for each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/account/login.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Log In&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {% raw %}{% csrf_token %}
  {{ form.as_p }}{% endraw %}
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log In&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/account/signup.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Sign Up&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {% raw %}{% csrf_token %}
  {{ form.as_p }}{% endraw %}
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign Up&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the new log in page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3ti3gzhimbujd65f7arq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3ti3gzhimbujd65f7arq.png" alt="New log in page" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the new sign up page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fww3a4xz242petfj9qtyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fww3a4xz242petfj9qtyh.png" alt="New sign up page" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But are we done? Haha, of course not. If you try out the forms you'll see they result in error pages. Why? We need to tell Django &lt;em&gt;where&lt;/em&gt; to redirect our user after they log in and sign up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhbtqsxc287zdpp80xxt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhbtqsxc287zdpp80xxt8.png" alt="Error page" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirects
&lt;/h2&gt;

&lt;p&gt;The setting for redirects is, not surprisingly, in our &lt;code&gt;settings.py&lt;/code&gt; file. Here are the two lines to add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;LOGIN_REDIRECT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_LOGOUT_REDIRECT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both reference a template with the named URL of &lt;code&gt;home&lt;/code&gt; so we'll need to create that homepage now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Homepage
&lt;/h2&gt;

&lt;p&gt;I like to create a dedicated &lt;code&gt;pages&lt;/code&gt; app for all static pages in my Django projects. Let's blast through that quickly which means also adding a new template, view, and url. Here we go...make sure to stop the local server with &lt;code&gt;Control+c&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(code) $ python manage.py startapp pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the new &lt;code&gt;pages&lt;/code&gt; app to our &lt;code&gt;INSTALLED_APPS&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sites&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# 3rd party
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth.account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth.socialaccount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# Local
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users.apps.UsersConfig&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages.apps.PagesConfig&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add it to our &lt;code&gt;config/urls.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accounts/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allauth.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the views file for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make a new &lt;code&gt;pages/urls.py&lt;/code&gt; file and fill it in like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can make our homepage template which will tell us if a user is logged in or not and provide helpful links.&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;templates/home.html&lt;/code&gt; file and add some basic logic for if the user is logged in or not. Be careful not to put this within the &lt;code&gt;templates/account&lt;/code&gt; directory, this is just in &lt;code&gt;templates&lt;/code&gt; itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/home.html --&amp;gt;&lt;/span&gt;
{% raw %}{% if user.is_authenticated %}
  Hi {{ user.email }}!
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'account_logout' %}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log Out&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
{% else %}
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You are not logged in&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'account_login' %}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log In&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; |
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'account_signup' %}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign Up&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
{% endif %}{% endraw %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew! Done. Make sure you are logged out which you can confirm by heading over quickly to the Django admin &lt;a href="http://127.0.0.1:8000/admin" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin&lt;/a&gt;. The "Log out" link is in the upper right corner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it all out
&lt;/h2&gt;

&lt;p&gt;Now go to our snazzy new homepage at &lt;a href="http://127.0.0.1:8000/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkew13y4lhsdmavapg11c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkew13y4lhsdmavapg11c.png" alt="Homepage" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click on "Log in" and use your superuser account it will redirect back to the homepage. Here's what mine looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7mrouot0oqfg7iyl6t7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7mrouot0oqfg7iyl6t7b.png" alt="Homepage logged in" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on the "Log out" link and then try the "Sign up" link. I've created a new user account called &lt;code&gt;testuser@email.com&lt;/code&gt;. Upon signing up I'm redirected back to the homepage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6rue2nip7d85vicg7u6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6rue2nip7d85vicg7u6k.png" alt="Homepage logged in with testuser" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;If you'd like to see a more full-featured Django starter project, my book &lt;a href="https://learndjango.com/books/" rel="noopener noreferrer"&gt;Django for Professionals&lt;/a&gt; covers all of this in great detail as well as using Docker, PostgreSQL, and a host of more advanced Django topics.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>What's New in Django 3.1</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Wed, 08 Jul 2020 19:37:36 +0000</pubDate>
      <link>https://forem.com/learndjango/what-s-new-in-django-3-1-320f</link>
      <guid>https://forem.com/learndjango/what-s-new-in-django-3-1-320f</guid>
      <description>&lt;p&gt;Django 3.1 will be released in early August 2020 and comes with a number of major new features and many minor improvements including asynchronous views and middleware support, asynchronous tests, JSONField for all supported database backends (not just PostgreSQL), an updated admin page, &lt;code&gt;SECURE_REFERRER_POLICY&lt;/code&gt;, and much more. The &lt;a href="https://docs.djangoproject.com/en/3.1/releases/3.1/" rel="noopener noreferrer"&gt;official release notes&lt;/a&gt; are the canonical reference, but this post will cover some of the highlights as well as tips for upgrading.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Upgrade
&lt;/h2&gt;

&lt;p&gt;You really should strive to be on the latest version of both Django and Python. This will result in faster, more supported, and feature-laden code. To upgrade, create a new virtual environment, install Django 3.1, and run your test suite (you have one, yes?) by adding the &lt;code&gt;-Wa&lt;/code&gt; flag to show full deprecation warnings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(env) $ python -Wa manage.py test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have tests in your project yet, you can still use &lt;code&gt;python -Wa manage.py runserver&lt;/code&gt; to see some warnings. Refer to the &lt;a href="https://docs.djangoproject.com/en/3.1/howto/upgrade-version/" rel="noopener noreferrer"&gt;official upgrade list&lt;/a&gt; for more advice. &lt;/p&gt;

&lt;p&gt;Django 3.1 requires Python 3.6, 3.7, or 3.8. You can read more about supported Python versions on the &lt;a href="https://docs.djangoproject.com/en/3.1/faq/install/#what-python-version-can-i-use-with-django" rel="noopener noreferrer"&gt;Django prerequisites page&lt;/a&gt;. If you're curious about Django's version and release policy, the &lt;a href="https://www.djangoproject.com/download/" rel="noopener noreferrer"&gt;download page&lt;/a&gt; shows the supported versions timeline and future LTS (long-term support) releases.&lt;/p&gt;

&lt;p&gt;For further context, the Django Chat podcast co-hosted by Django Fellow Carlton Gibson has episodes on &lt;a href="https://djangochat.com/episodes/django-versions" rel="noopener noreferrer"&gt;Django Versions&lt;/a&gt; and &lt;a href="https://djangochat.com/episodes/security-releases" rel="noopener noreferrer"&gt;Django Security Releases&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asynchronous Views, Middleware, Tests
&lt;/h2&gt;

&lt;p&gt;With version 3.0, Django began its async journey in earnest with &lt;a href="https://asgi.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;ASGI&lt;/a&gt; support. In 3.1, Django now supports a fully asychronous request path with views, middleware, and tests/test client.&lt;/p&gt;

&lt;p&gt;A basic example, provided in the docs, is to make a request that waits for half a second and then returns a response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# views.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, async world!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works whether you are running WSGI or &lt;a href="https://asgi.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;ASGI&lt;/a&gt; mode, which was first added in 3.0.&lt;/p&gt;

&lt;p&gt;A more relevant example would be to asynchronously fetch a URL in Django using &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt;, the next-generation version of the popular &lt;a href="https://github.com/psf/requests" rel="noopener noreferrer"&gt;requests&lt;/a&gt; library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-db JSONField
&lt;/h2&gt;

&lt;p&gt;JSONField support for &lt;a href="https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.JSONField" rel="noopener noreferrer"&gt;models&lt;/a&gt; and &lt;a href="https://docs.djangoproject.com/en/3.1/ref/forms/fields/#django.forms.JSONField" rel="noopener noreferrer"&gt;forms&lt;/a&gt; has now been extended to all Django-supported backends (MariaDB, MySQL, Oracle, and SQLite), not just PostgreSQL. This provides full support for &lt;a href="https://docs.djangoproject.com/en/3.1/topics/db/queries/#querying-jsonfield" rel="noopener noreferrer"&gt;JSONField queries&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; 

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SoccerInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSONField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;SoccerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Liverpool FC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;coaches&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Klopp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Krawietz&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;players&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;forwards&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Salah&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Firmino&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mané&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;SoccerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;data__team&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Liverpool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data__coaches__contains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Klopp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data__players__has_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;forwards&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Admin Layout
&lt;/h2&gt;

&lt;p&gt;The admin now has a sidebar on the lefthand side for easier naviation on large screens. The historical breadcrumbs pattern is still available. If you'd like to disable the new sidebar, set &lt;a href="https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.AdminSite.enable_nav_sidebar" rel="noopener noreferrer"&gt;AdminSite.enable_nav_sidebar&lt;/a&gt; to &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the older admin view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwy4yqts1sqd69vo1y5ke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwy4yqts1sqd69vo1y5ke.png" alt="Django 3.0 Admin" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the new admin view with sidebar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhv0wiobirpx7doap5pgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhv0wiobirpx7doap5pgw.png" alt="Django 3.1 Admin" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  pathlib
&lt;/h2&gt;

&lt;p&gt;Django has switched from using &lt;a href="https://docs.python.org/3.8/library/os.path.html" rel="noopener noreferrer"&gt;os.path&lt;/a&gt; to the more modern and concise &lt;a href="https://docs.python.org/3/library/pathlib.html" rel="noopener noreferrer"&gt;pathlib&lt;/a&gt;. If you create a new project using the &lt;code&gt;startproject&lt;/code&gt; command, the automatically generated &lt;code&gt;settings.py&lt;/code&gt; file now defaults to &lt;code&gt;pathlib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the Django 3.0 version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the newer Django 3.1 version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SECURE_REFERRER_POLICY
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.djangoproject.com/en/3.1/ref/settings/#std:setting-SECURE_REFERRER_POLICY" rel="noopener noreferrer"&gt;SECURE_REFERRER_POLICY&lt;/a&gt; now defaults to 'same-origin', which is more secure. This is one of many steps the Django team makes around security. You can--and should--run the &lt;a href="https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/" rel="noopener noreferrer"&gt;Django deployment checklist&lt;/a&gt; to ensure your project is secure before pushing to production: &lt;code&gt;python manage.py check --deploy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are many more new features, extensively documented in the &lt;a href="https://docs.djangoproject.com/en/3.1/releases/3.1/" rel="noopener noreferrer"&gt;official release notes&lt;/a&gt;, which are well-worth reading in full as well as the accompanying Django 3.1 documentation.&lt;/p&gt;

&lt;p&gt;Every major new Django release is a team effort and 3.1 is no exception. The Django Fellows, &lt;a href="https://twitter.com/carltongibson" rel="noopener noreferrer"&gt;Carlton Gibson&lt;/a&gt; and &lt;a href="https://twitter.com/MariuszFelisiak" rel="noopener noreferrer"&gt;Mariusz Feliask&lt;/a&gt;, are resonsible for an enormous amount of work to ensure the release process occurs smoothly and on time. Particular thanks are due to &lt;a href="https://twitter.com/andrewgodwin?" rel="noopener noreferrer"&gt;Andrew Godwin&lt;/a&gt; for his work on async features and &lt;a href="https://twitter.com/laymonage" rel="noopener noreferrer"&gt;Sage Abdullah&lt;/a&gt; for adding cross-db JSONFields.&lt;/p&gt;

&lt;p&gt;If you'd like to support the continued development of Django, please do so on the &lt;a href="https://www.djangoproject.com/fundraising/" rel="noopener noreferrer"&gt;official fundraising page&lt;/a&gt; or by becoming a &lt;a href="https://github.com/sponsors/django" rel="noopener noreferrer"&gt;GitHub Sponsor&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Django Static Files Tutorial</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Thu, 02 Jul 2020 15:18:03 +0000</pubDate>
      <link>https://forem.com/learndjango/django-static-files-tutorial-1fg7</link>
      <guid>https://forem.com/learndjango/django-static-files-tutorial-1fg7</guid>
      <description>&lt;p&gt;Static files are a common source of confusion for Django newcomers. The term "static files" refers to files like CSS, JavaScript, or images that do not change in a web app. They remain static. For local development, static files are served up by the local Django web server and minimal configuration is required. Django does not support serving static files in production, however, so a number of additional configurations are required to get them working.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll look at how static files work in Django, how to configure them locally, as well as using &lt;a href="http://whitenoise.evans.io/en/stable/" rel="noopener noreferrer"&gt;WhiteNoise&lt;/a&gt; to serve them in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Development
&lt;/h2&gt;

&lt;p&gt;When you first run &lt;code&gt;startproject&lt;/code&gt; to create a new Django project, a &lt;code&gt;settings.py&lt;/code&gt; file is automatically created which defaults to local development settings. These include &lt;code&gt;DEBUG&lt;/code&gt; set to &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;DATABASES&lt;/code&gt; set to SQLite. It also includes, near the bottom of the file, a solitary line for static files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/static/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;STATIC_URL&lt;/code&gt; setting controls the actual URL used to locate static files. In this case, that would be &lt;code&gt;/static/&lt;/code&gt;, so in local development, &lt;code&gt;127.0.0.1:8000/static/&lt;/code&gt; or &lt;code&gt;localhost:8000/static/&lt;/code&gt;. In production, if our website was called &lt;code&gt;example.com&lt;/code&gt;, the static files would be located at &lt;code&gt;example.com/static/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two steps required to use static files locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a &lt;code&gt;static&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;{% load static %}&lt;/code&gt; at the top of a template and use the &lt;code&gt;static&lt;/code&gt; template tag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;static&lt;/code&gt; directory should be in the same folder as &lt;code&gt;manage.py&lt;/code&gt;. Add any desired static files here. For example, you might create another folder called &lt;code&gt;css&lt;/code&gt; and within it a file called &lt;code&gt;base.css&lt;/code&gt;. To include this file in a template, make sure the first line of the file is &lt;code&gt;{% load static %}&lt;/code&gt; and the link would look something like this: &lt;code&gt;&amp;lt;link href="{% static 'css/base.css' %}" rel="stylesheet"&amp;gt;&lt;/code&gt;. The &lt;code&gt;static&lt;/code&gt; template tag is used variable, could also hardcode the result here. This is useful later on for production.&lt;/p&gt;

&lt;p&gt;That's it!&lt;/p&gt;

&lt;h2&gt;
  
  
  collectstatic
&lt;/h2&gt;

&lt;p&gt;For production, Django looks through the entire project for static files (&lt;code&gt;STATICFILES_DIRS&lt;/code&gt;) and collects all available static files via the &lt;code&gt;collectstatic&lt;/code&gt; command into a dedicated directory (&lt;code&gt;STATIC_ROOT&lt;/code&gt;). The &lt;em&gt;way&lt;/em&gt; in which the files are stored is dictated by &lt;code&gt;STATICFILES_STORAGE&lt;/code&gt;. We therefore need to add these three additional configurations and run the &lt;code&gt;collectstatic&lt;/code&gt; command in production every time there is a change to the static files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;STATICFILES_DIRS&lt;/code&gt; tells Django &lt;em&gt;where to look&lt;/em&gt; for static files in a Django project. Often, this is simply the &lt;code&gt;static&lt;/code&gt; directory but there could be static directories in various apps or other locations. When the &lt;code&gt;collectstatic&lt;/code&gt; command is run, Django refers to the list of directories here when creating a dedicated directory for production serving.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;STATIC_ROOT&lt;/code&gt; is the &lt;em&gt;location&lt;/em&gt; of the collected static files, often named &lt;code&gt;staticfiles&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;STATICFILES_STORAGE&lt;/code&gt; is the &lt;em&gt;file storage engine&lt;/em&gt; used when collecting static files with the &lt;code&gt;collecstatic&lt;/code&gt; command. By default, it is implicitly set to &lt;code&gt;django.contrib.staticfiles.storage.StaticFilesStorage&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;In your local Django project, add these three settings to your &lt;code&gt;settings.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/static/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;STATICFILES_DIRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;static&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),]&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="n"&gt;STATICFILES_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles.storage.StaticFilesStorage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the command &lt;code&gt;python manage.py collectstatic&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(env) $ python manage.py collectstatic
You have requested to collect static files at the destination
location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; to continue and hit the Enter key. A new &lt;code&gt;staticfiles&lt;/code&gt; directory will be created which has folders for &lt;code&gt;admin&lt;/code&gt; (the built-in admin has its own static files), &lt;code&gt;staticfiles.json&lt;/code&gt;, and whatever directories are in your &lt;code&gt;static&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;If you now add a new static file to &lt;code&gt;static&lt;/code&gt;, it will be available for local usage. It is only for production where the file won't be present, unless you run &lt;code&gt;python manage.py collectstatic&lt;/code&gt; each and every time. For this reason, running &lt;code&gt;collectstatic&lt;/code&gt; is typically added to deployment pipelines and is done by default on Heroku.&lt;/p&gt;

&lt;h2&gt;
  
  
  WhiteNoise
&lt;/h2&gt;

&lt;p&gt;Even though we've configured our Django project to collect static files properly, there's one more step involved which is not included in the official Django docs. That is the configuration of &lt;a href="http://whitenoise.evans.io/en/stable/" rel="noopener noreferrer"&gt;WhiteNoise&lt;/a&gt;, radically simplified static file serving for Python web apps. Is it possible to use another package to serve Python static files? Yes. Have I ever seen it done over the last 5 years? No. Everyone uses WhiteNoise and you should, too, since Django won't serve static files in production on its own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(env) $ pipenv install whitenoise==5.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the &lt;code&gt;settings.py&lt;/code&gt; file, add &lt;code&gt;whitenoise&lt;/code&gt; to the &lt;code&gt;INSTALLED_APPS&lt;/code&gt; &lt;strong&gt;above&lt;/strong&gt; the built-in &lt;code&gt;staticfiles&lt;/code&gt; app. Under &lt;code&gt;MIDDLEWARE&lt;/code&gt;, add a new &lt;code&gt;WhiteNoiseMiddleware&lt;/code&gt; on the third line. And at the bottom of the file, change &lt;code&gt;STATICFILES_STORAGE&lt;/code&gt; to use WhiteNoise. It should look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whitenoise.runserver_nostatic&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.security.SecurityMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions.middleware.SessionMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whitenoise.middleware.WhiteNoiseMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.common.CommonMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.csrf.CsrfViewMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.middleware.AuthenticationMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages.middleware.MessageMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.clickjacking.XFrameOptionsMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/static/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;STATICFILES_DIRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;static&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),]&lt;/span&gt;
&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;STATICFILES_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whitenoise.storage.CompressedManifestStaticFilesStorage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Run &lt;code&gt;python manage.py collectstatic&lt;/code&gt; again so that the files are stored using WhiteNoise. And then deploy with confidence to the hosting platform of your choice.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Django Slug Tutorial</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Thu, 02 Jul 2020 15:13:42 +0000</pubDate>
      <link>https://forem.com/learndjango/django-slug-tutorial-4all</link>
      <guid>https://forem.com/learndjango/django-slug-tutorial-4all</guid>
      <description>&lt;p&gt;In this tutorial we will add slugs to a Django website. As noted in the &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#slugfield" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;: "Slug is a newspaper term. A slug is a short label for something, containing only letters, numbers, underscores or hyphens. They’re generally used in URLs."&lt;/p&gt;

&lt;p&gt;To give a concrete example, assume you had a Newspaper website (such as we'll build in this tutorial). For a story titled "Hello World," the URL would be &lt;code&gt;example.com/hello-world&lt;/code&gt; assuming the site was called &lt;code&gt;example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Despite their ubiquity, slugs can be somewhat challenging to implement the first time around, at least in my experience. Therefore we will implement everything from scratch so you can see how the pieces all fit together. If you are already comfortable implementing ListView and DetailView you can jump right to the Slug section.&lt;/p&gt;

&lt;p&gt;Complete source code can be found &lt;a href="https://github.com/wsvincent/django-slug-tutorial" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up
&lt;/h2&gt;

&lt;p&gt;To start let's navigate into a directory for our code. This can be hosted anywhere on your computer but if you're on a Mac, an easy-to-find location is the &lt;code&gt;Desktop&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ~/Desktop
$ mkdir newspaper &amp;amp;&amp;amp; cd newspaper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To pay homage to &lt;a href="https://en.wikipedia.org/wiki/Django_(web_framework)" rel="noopener noreferrer"&gt;Django's origin at a newspaper&lt;/a&gt;, we'll create a basic &lt;em&gt;Newspaper&lt;/em&gt; website with Articles. If you need help installing Python, Django, and all the rest (&lt;a href="https://djangoforbeginners.com/initial-setup/" rel="noopener noreferrer"&gt;see here for in-depth instructions&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;On your command line, enter the following commands to install the latest version with &lt;code&gt;Pipenv&lt;/code&gt;, create a project called &lt;code&gt;config&lt;/code&gt;, set up the initial database via &lt;code&gt;migrate&lt;/code&gt;, and then start the local web server with &lt;code&gt;runserver&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pipenv install django~=3.0.0
$ pipenv shell
(newspaper) $ django-admin startproject config .
(newspaper) $ python manage.py migrate
(newspaper) $ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to include that period &lt;code&gt;.&lt;/code&gt; at the end of the &lt;code&gt;startproject&lt;/code&gt; command! It is an optional step that avoid Django creating an additional directory otherwise.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="http://127.0.0.1:8000/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/&lt;/a&gt; in your web browser to see the Django welcome page which confirms everything is configured properly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx5920mjxm0qxm1zrm2ii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx5920mjxm0qxm1zrm2ii.png" alt="Django welcome page" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Articles app
&lt;/h2&gt;

&lt;p&gt;Since the focus of this tutorial is on slugs I'm going to simply give the commands and code to wire up this Articles app. Full explanations can be found in my book &lt;a href="https://djangoforbeginners.com" rel="noopener noreferrer"&gt;Django for Beginners&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Let's start by creating an app called &lt;code&gt;articles&lt;/code&gt;. Stop the local server with &lt;code&gt;Control+c&lt;/code&gt; and use the &lt;code&gt;startapp&lt;/code&gt; command to create this new app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py startapp articles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update &lt;code&gt;INSTALLED_APPS&lt;/code&gt; within our &lt;code&gt;config/settings.py&lt;/code&gt; file to notify Django about the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;articles.apps.ArticlesConfig&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Article Model
&lt;/h2&gt;

&lt;p&gt;Create the database model which will have a &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt;. We'll also set &lt;code&gt;__str__&lt;/code&gt; and a &lt;code&gt;get_absolute_url&lt;/code&gt; which are Django best practices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a migrations file for this change, then add it to our database via &lt;code&gt;migrate&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py makemigrations articles
(newspaper) $ python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Django Admin
&lt;/h2&gt;

&lt;p&gt;The Django admin is a convenient way to play around with models so we'll use it. But first, create a superuser account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py createsuperuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then update &lt;code&gt;articles/admin.py&lt;/code&gt; to display our app within the admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/admin.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start up the server again with &lt;code&gt;python manage.py runserver&lt;/code&gt; and navigate to the admin at &lt;a href="http://127.0.0.1:8000/admin" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin&lt;/a&gt;. Log in with your superuser account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbzsv4ksotxihmj1h5ljp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbzsv4ksotxihmj1h5ljp.png" alt="Admin Homepage" width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the "+ Add" next to the &lt;code&gt;Articles&lt;/code&gt; section and add an entry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8g2cx38gf46qno6srjas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8g2cx38gf46qno6srjas.png" alt="Admin Article" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  URLs
&lt;/h2&gt;

&lt;p&gt;In addition to a model we'll eventually need a URL, view, and template to display an Article page. I like to move to URLs next after models although the order doesn't matter: we need all four before we can display a single page. The first step is to add the &lt;code&gt;articles&lt;/code&gt; app to our project-level &lt;code&gt;config/urls.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;articles.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we must create the app-level &lt;code&gt;urls.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touch articles/urls.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll have a &lt;a href="https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-display/#listview" rel="noopener noreferrer"&gt;ListView&lt;/a&gt; to list all articles and a &lt;a href="https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-display/#detailview" rel="noopener noreferrer"&gt;DetailView&lt;/a&gt; for individual articles.&lt;/p&gt;

&lt;p&gt;Note that we're referencing two views that have yet to be created: &lt;code&gt;ArticleListView&lt;/code&gt; and &lt;code&gt;ArticleDetailView&lt;/code&gt;. We'll add them in the next section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArticleListView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleDetailView&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;int:pk&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleDetailView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleListView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Views
&lt;/h2&gt;

&lt;p&gt;For each view we specify the related model and appropriate not-yet-created template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DetailView&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_list.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleDetailView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DetailView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Templates
&lt;/h2&gt;

&lt;p&gt;Finally, we come to the last step: templates. By default Django will look within each app for a &lt;code&gt;templates&lt;/code&gt; directory. That structure in our case would be &lt;code&gt;articles/templates/template.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;Control+c&lt;/code&gt; on the command line and create the new &lt;code&gt;templates&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ mkdir articles/templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the two new templates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ touch articles/templates/article_list.html
(newspaper) $ touch articles/templates/article_detail.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For our list page we loop over &lt;code&gt;object_list&lt;/code&gt; which is provided by &lt;code&gt;ListView&lt;/code&gt;. And we add an &lt;code&gt;a href&lt;/code&gt; by using the &lt;code&gt;get_absolute_url&lt;/code&gt; method added to the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- article_list.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Articles&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
{% for article in object_list %}
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ article.get_absolute_url }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ article.title }}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detail view outputs our two fields--&lt;code&gt;title&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt;--using the &lt;code&gt;object&lt;/code&gt; default provided by DetailView. You can, and probably should, rename both &lt;code&gt;object_list&lt;/code&gt; in the ListView and &lt;code&gt;object&lt;/code&gt; in the DetailView to be more descriptive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- article_detail.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ object.title }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ object.body }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the server is running--&lt;code&gt;python manage.py runserver&lt;/code&gt;--and check out both our pages in your web browser.&lt;/p&gt;

&lt;p&gt;The list of all articles is available at &lt;code&gt;http://127.0.0.1:8000/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F44xcif78yldvo9ypao3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F44xcif78yldvo9ypao3c.png" alt="ListView" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the detail view for our single article is at &lt;code&gt;http://127.0.0.1:8000/1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0b04mgx45imj8j5fjowr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0b04mgx45imj8j5fjowr.png" alt="DetailView" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Slugs
&lt;/h2&gt;

&lt;p&gt;Finally we come to slugs. Ultimately we want our article title to be reflected in the URL. In other words, "A Day in the Life" should have the URL of &lt;code&gt;http://127.0.0.1:8000/a-day-in-the-life&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are only two steps required: updating our &lt;code&gt;articles/models.py&lt;/code&gt; file and &lt;code&gt;articles/urls.py&lt;/code&gt;. Ready? Let's go...&lt;/p&gt;

&lt;p&gt;In our model, we can add Django's built-in &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#slugfield" rel="noopener noreferrer"&gt;SlugField&lt;/a&gt;. But we must &lt;strong&gt;also&lt;/strong&gt;--and this is the part that typically trips people up--update &lt;code&gt;get_absolute_url&lt;/code&gt; as well. That's where we pass in the value used in our URL. Currently it passes in the &lt;code&gt;id&lt;/code&gt; for the article an &lt;code&gt;args&lt;/code&gt;, so &lt;code&gt;1&lt;/code&gt; for our first article. We need to change that over to a keyword argument, &lt;code&gt;kwargs&lt;/code&gt;, for our &lt;code&gt;slug&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SlugField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving along let's add a migration file since we've updated our model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py makemigrations articles
You are trying to add a non-nullable field 'slug' to article without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ack! What is this?! It turns out we &lt;em&gt;already&lt;/em&gt; have data in our database, our single Article, so we can't just willy-nilly add a new field on. Django is helpfully telling us that we either need to a a one-off default of &lt;code&gt;null&lt;/code&gt; or add it ourself. Hmmm.&lt;/p&gt;

&lt;p&gt;For this very reason, it is generally good advice to always add new fields with either &lt;code&gt;null=True&lt;/code&gt; or with a &lt;code&gt;default&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Let's take the easy approach of setting &lt;code&gt;null=True&lt;/code&gt; for now. So type &lt;code&gt;2&lt;/code&gt; on the command line. Then add this to our &lt;code&gt;slug&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SlugField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try to create a migrations file again and it will work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py makemigrations articles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go ahead and &lt;code&gt;migrate&lt;/code&gt; the database as well to apply the change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you think about it, what we've done is create a &lt;code&gt;null&lt;/code&gt; value for our &lt;code&gt;slug&lt;/code&gt;. We have to go into the admin to set it properly. Start up the local server, &lt;code&gt;python manage.py runserver&lt;/code&gt;, and go to the Article page in the Admin. The &lt;code&gt;Slug&lt;/code&gt; field is empty.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvfn62lv9129vwoaux0o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvfn62lv9129vwoaux0o4.png" alt="Empty Slug" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Manually add in our desired value &lt;code&gt;a-day-in-the-life&lt;/code&gt; and click "Save."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbwvb09dcnh7xts1vm3rp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbwvb09dcnh7xts1vm3rp.png" alt="Add Slug" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, last step is to update &lt;code&gt;articles/urls.py&lt;/code&gt; so that we display the &lt;code&gt;slug&lt;/code&gt; keyword argument in the URL itself. Luckily that just means swapping out &lt;code&gt;&amp;lt;int:pk&amp;gt;&lt;/code&gt; for &lt;code&gt;&amp;lt;slug:slug&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArticleListView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleDetailView&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;slug:slug&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleDetailView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleListView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we're done! Go to the list view page at &lt;code&gt;http://127.0.0.1:8000/&lt;/code&gt; and click on the link for our article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4n88ocu5n32yilxit0rj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4n88ocu5n32yilxit0rj.png" alt="Slug URL" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And there it is with our slug as the URL! Beautiful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unique and Null
&lt;/h2&gt;

&lt;p&gt;Moving forward, do we really want to allow a &lt;code&gt;null&lt;/code&gt; value for a slug? Probably not as it will break our site! Another consideration is: what happens if there are identical slugs? How will that resolve itself? The short answer is: not well.&lt;/p&gt;

&lt;p&gt;Therefore let's change our &lt;code&gt;slug&lt;/code&gt; field so that &lt;code&gt;null&lt;/code&gt; is not allowed and unique values are required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SlugField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make migration/migrate. Need empty for past entry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py makemigrations articles
You are trying to change the nullable field 'slug' on article to non-nullable without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration)
 3) Quit, and let me add a default in models.py
Select an option:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select &lt;code&gt;2&lt;/code&gt; since we can manually handle the existing row ourself, and in fact, already have. Then &lt;code&gt;migrate&lt;/code&gt; the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(newspaper) $ python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  PrePopulated Fields
&lt;/h2&gt;

&lt;p&gt;Manually adding a &lt;code&gt;slug&lt;/code&gt; field each time quickly becomes tedious. So we can use a &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.prepopulated_fields" rel="noopener noreferrer"&gt;prepopulated_field&lt;/a&gt; in the admin to automate the process for us.&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;articles/admin.py&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/admin.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="n"&gt;prepopulated_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,)}&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now head over to the admin and add a new article. You'll note that as you type in the &lt;code&gt;Title&lt;/code&gt; field, the &lt;code&gt;Slug&lt;/code&gt; field is automatically populated. Pretty neat!&lt;/p&gt;

&lt;h2&gt;
  
  
  Signals, Lifecycle Hooks, Save, and Forms/Serializers
&lt;/h2&gt;

&lt;p&gt;In the real world, it's unlikely to simply provide admin access to a user. You could, but at scale it's definitely not a good idea. And even on a small scale, most non-technical users will find a web interface more appealing.&lt;/p&gt;

&lt;p&gt;So...how to auto-populate the &lt;code&gt;slug&lt;/code&gt; field when creating a new Article. It turns out Django has a built-in tool for this called &lt;a href="https://docs.djangoproject.com/en/3.0/ref/utils/#django.utils.text.slugify" rel="noopener noreferrer"&gt;slugify&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;But how to use &lt;code&gt;slugify&lt;/code&gt;? In practice, it's common to see this done with a &lt;a href="https://docs.djangoproject.com/en/3.0/topics/signals/#module-django.dispatch" rel="noopener noreferrer"&gt;Signal&lt;/a&gt;. But I would argue--as would Django Fellow Carlton Gibson--that this is not a good use of signals because we know both the sender and receiver here. There's no mystery. We discuss the proper use of signals at length in our &lt;a href="https://djangochat.com/episodes/signals" rel="noopener noreferrer"&gt;Django Chat Podcast episode&lt;/a&gt; on the topic.&lt;/p&gt;

&lt;p&gt;An alternative to signals is to use a lifecycle hook via something like the &lt;a href="https://github.com/rsinger86/django-lifecycle" rel="noopener noreferrer"&gt;django-lifecycle&lt;/a&gt; package. Lifecycle hooks are an alternative to Signals that provide similar functionality with less indirection.&lt;/p&gt;

&lt;p&gt;Another common way to see this implemented is by overriding the &lt;code&gt;Article&lt;/code&gt; model's &lt;a href="https://docs.djangoproject.com/en/3.0/topics/db/models/#overriding-model-methods" rel="noopener noreferrer"&gt;save method&lt;/a&gt;. This also "works" but is not the best solution. Here is one way to do that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# articles/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.template.defaultfilters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;slugify&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SlugField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best solution, in my opinion, is to create the slug in the form itself. This can be done by overriding the form's &lt;a href="https://docs.djangoproject.com/en/3.0/ref/forms/validation/#validating-fields-with-clean" rel="noopener noreferrer"&gt;clean method&lt;/a&gt; so that &lt;code&gt;cleaned_data&lt;/code&gt; has the slug, or JavaScript can be used to auto-populate the field as is done in the Django admin itself. If you're using an API, the same approach can be applied to the serializer.&lt;/p&gt;

&lt;p&gt;This solution does not rely on a custom signal and handles the slug &lt;strong&gt;before&lt;/strong&gt; it touches the database. In the words of Carlton Gibson, who suggested this approach to me, win-win-win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Words of Caution
&lt;/h2&gt;

&lt;p&gt;A quick word of caution on using slugs in a large website. Despite requiring an Article to be &lt;code&gt;unique&lt;/code&gt; it is almost inevitable to have naming conflicts using slugs. However slugs do seem to have good SEO properties. A good compromise is to combine a slug with a &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#uuidfield" rel="noopener noreferrer"&gt;UUID&lt;/a&gt; or a username. The ultimate URL would therefore be &lt;code&gt;{{ uuid }}/{{ slug }}&lt;/code&gt; or &lt;code&gt;{{ username }}/{{ slug }}&lt;/code&gt;. If you look at Github, for example, the source code for this tutorial is at &lt;a href="https://github.com/wsvincent/django-slug-tutorial" rel="noopener noreferrer"&gt;https://github.com/wsvincent/django-slug-tutorial&lt;/a&gt; which is a username + slug pattern. While you &lt;em&gt;could&lt;/em&gt; also use an &lt;code&gt;id&lt;/code&gt; + a slug, using ids is often a security concern and in my opinion should be avoided for production websites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;If you want to compare your code with the official source code, it can be found &lt;a href="https://github.com/wsvincent/django-slug-tutorial" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Learn Django (2020)</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Sun, 21 Jun 2020 20:05:04 +0000</pubDate>
      <link>https://forem.com/learndjango/how-to-learn-django-2020-3g45</link>
      <guid>https://forem.com/learndjango/how-to-learn-django-2020-3g45</guid>
      <description>&lt;p&gt;As a “batteries-included” web framework, Django comes with a host of built-in features and a correspondingly steep learning curve for newcomers. In this post, I discuss what you need to know &lt;em&gt;before&lt;/em&gt; giving Django a proper go and links to recommended resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML/CSS
&lt;/h2&gt;

&lt;p&gt;Web pages are made out of HTML and CSS. Knowing how to build and deploy static websites is &lt;strong&gt;highly recommended&lt;/strong&gt; before embarking on an attempt at Django. Fortunately, there are several good free resources available including &lt;a href="https://www.freecodecamp.org/" rel="noopener noreferrer"&gt;FreeCodeCamp&lt;/a&gt;; Shay How’s &lt;a href="https://learn.shayhowe.com/" rel="noopener noreferrer"&gt;Learn to Code HTML &amp;amp; CSS&lt;/a&gt; series, and &lt;a href="https://www.internetingishard.com/html-and-css/" rel="noopener noreferrer"&gt;HTML &amp;amp; CSS is Hard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;HTML itself is not &lt;em&gt;that&lt;/em&gt; deep a topic. You can learn the basics in a day and master most of you’ll need within a week. CSS is, unfortunately, far more complex. You don’t need to become a CSS expert, but you should know how it interacts with HTML and be able to style your static websites somewhat. In practice, most developers rely on a CSS framework like &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap&lt;/a&gt; or &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, and in companies Django developers typically don’t touch the front-end at all, so a basic understanding is all that you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  World Wide Web
&lt;/h2&gt;

&lt;p&gt;It’s necessary to also have a fundamental understanding of how the World Wide Web actually works. Mozilla has probably the best guide called &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/How_the_Web_works" rel="noopener noreferrer"&gt;How the Web works&lt;/a&gt; which is part of its larger, and also recommended, &lt;a href="https://developer.mozilla.org/en-US/docs/Learn" rel="noopener noreferrer"&gt;Learn Web Development&lt;/a&gt; series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;Django is written entirely in the Python programming language so it shouldn’t be surprising that Python knowledge is part of the list of prerequisites. The question, though, is how much Python do you need to know? Obviously the more the better but I would argue you don’t need to be a Python expert to use Django. At a minimum, you should understand how to install Python packages (like Django), use a virtual environment, imports, and classes. &lt;a href="https://realpython.com/" rel="noopener noreferrer"&gt;RealPython&lt;/a&gt; is a popular source of Python tutorials but if you’re looking for a book, &lt;a href="https://amzn.to/2okggMH" rel="noopener noreferrer"&gt;Python Crash Course&lt;/a&gt; covers the basics and is enough background to embark on Django itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Databases &amp;amp; SQL
&lt;/h2&gt;

&lt;p&gt;A database-driven website relies on, well, databases so you should have a basic understanding of how SQL works as well as database design principles. &lt;a href="https://www.khanacademy.org/computing/computer-programming/sql" rel="noopener noreferrer"&gt;Khan Academy&lt;/a&gt; has a free guide to SQL and this site has a &lt;a href="https://learndjango.com/tutorials/database-design-tutorial-beginners" rel="noopener noreferrer"&gt;Database Design Tutorial for Beginners&lt;/a&gt; that is also worth reading to understand primary keys, one-to-many relationships, and database normalization.&lt;/p&gt;

&lt;p&gt;When using Django itself, the ORM abstracts away the need to write raw SQL but understanding how databases work becomes increasingly essential as websites grow in size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git
&lt;/h2&gt;

&lt;p&gt;The final prerequisite is knowledge of Git, the version control system you’ll use on every project. It’s not technically part of Django but you’ll use it on any serious side or professional project. Github has a &lt;a href="https://guides.github.com/introduction/git-handbook/" rel="noopener noreferrer"&gt;Git Handbook&lt;/a&gt; that is a good first step. You should understand how to install git on a new repository, make commits, and push/pull code to a remote repository either GitHub, GitLab, or BitBucket most likely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django Tutorials
&lt;/h2&gt;

&lt;p&gt;Finally we come to Django itself whose official docs feature a &lt;a href="https://www.djangoproject.com/start/" rel="noopener noreferrer"&gt;Start&lt;/a&gt; page. The official &lt;a href="https://docs.djangoproject.com/en/dev/intro/tutorial01/" rel="noopener noreferrer"&gt;Polls tutorial&lt;/a&gt; is a good place to start but is not a friendly welcome for those new to web development with frameworks. It does not cover how to install Python or deploy your site to the internet, and goes in-depth on some areas of Django itself.&lt;/p&gt;

&lt;p&gt;If you find the official tutorial too much, a gentler introduction can be found in the &lt;a href="https://tutorial.djangogirls.org/en/" rel="noopener noreferrer"&gt;Django Girls tutorial&lt;/a&gt; on building and deploying a blog, or the sample chapters of &lt;a href="https://djangoforbeginners.com" rel="noopener noreferrer"&gt;Django for Beginners&lt;/a&gt; which cover the building of three Django sites. Mozilla also has a comprehensive though slightly more advanced &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django" rel="noopener noreferrer"&gt;guide to Django&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Books
&lt;/h2&gt;

&lt;p&gt;For a book-length treatment, &lt;a href="https://djangoforbeginners.com" rel="noopener noreferrer"&gt;Django for Beginners&lt;/a&gt; and &lt;a href="https://www.feldroy.com/products/django-crash-course" rel="noopener noreferrer"&gt;Django Crash Course&lt;/a&gt; are friendly beginner treatments. For intermediate/advanced coverage, &lt;a href="https://www.feldroy.com/products/two-scoops-of-django-3-x" rel="noopener noreferrer"&gt;Two Scoops of Django&lt;/a&gt;, Django for Professionals(&lt;a href="https://djangoforprofessionals.com" rel="noopener noreferrer"&gt;https://djangoforprofessionals.com&lt;/a&gt;), and &lt;a href="https://gumroad.com/l/suydt" rel="noopener noreferrer"&gt;Speed Up Your Django Tests&lt;/a&gt; are recommended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Videos
&lt;/h2&gt;

&lt;p&gt;There are many YouTube videos on Django and the quality varies considerably. Solid options include &lt;a href="https://www.youtube.com/watch?v=UmljXZIypDc" rel="noopener noreferrer"&gt;Corey Schafer’s series&lt;/a&gt; as well as those by &lt;a href="https://youtu.be/e1IyzVyrLSU" rel="noopener noreferrer"&gt;Traversy Media&lt;/a&gt;. For a paid option, &lt;a href="https://justdjango.com/" rel="noopener noreferrer"&gt;JustDjango&lt;/a&gt; is quality content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Podcasts
&lt;/h2&gt;

&lt;p&gt;At the moment, there is only one weekly podcast on Django called &lt;a href="https://djangochat.com/" rel="noopener noreferrer"&gt;Django Chat&lt;/a&gt;, hosted by the creator of LearnDjango.com and a Django Fellow, Carlton Gibson. &lt;a href="https://www.mattlayman.com/django-riffs/" rel="noopener noreferrer"&gt;Django Riffs&lt;/a&gt; is a budding series and &lt;a href="https://runninginproduction.com/tags/django" rel="noopener noreferrer"&gt;Running in Production&lt;/a&gt; often features Django-specific content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conferences &amp;amp; Meetups
&lt;/h2&gt;

&lt;p&gt;The best way to learn more and become involved in the community is to attend either a DjangoCon or local Meetup. &lt;a href="https://2021.djangocon.us/" rel="noopener noreferrer"&gt;DjangoCon US&lt;/a&gt; has been delayed until 2021 although &lt;a href="https://2020.djangocon.africa/" rel="noopener noreferrer"&gt;DjangoCon Africa&lt;/a&gt; is scheduled for November, and both &lt;a href="https://2020.djangocon.eu/" rel="noopener noreferrer"&gt;DjangoConEurope&lt;/a&gt; and &lt;a href="https://2020.pycon.org.au/" rel="noopener noreferrer"&gt;PyCon Australia&lt;/a&gt; are holding virtual conferences. In addition, there are Django meetups in most major cities that meet monthly and even, these days, virtually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django Forum
&lt;/h2&gt;

&lt;p&gt;If you’re stuck on a Django issue, Stack Overflow is always an option but a newer one is the official &lt;a href="https://forum.djangoproject.com/" rel="noopener noreferrer"&gt;Django Forum&lt;/a&gt; which has a wealth of good advice from experts in the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Ultimately, learning Django is a constant endeavor. The framework continues to evolve as does the broader World Wide Web, most notably with the current introduction of Async features in Django 3.0+. With a major new release &lt;a href="https://www.djangoproject.com/download/" rel="noopener noreferrer"&gt;scheduled for every 9 months&lt;/a&gt;, there’s never been a better time to learn Django.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Django "Hello, World" 5 Different Ways</title>
      <dc:creator>Will Vincent</dc:creator>
      <pubDate>Fri, 12 Jun 2020 14:23:12 +0000</pubDate>
      <link>https://forem.com/learndjango/django-hello-world-5-different-ways-209f</link>
      <guid>https://forem.com/learndjango/django-hello-world-5-different-ways-209f</guid>
      <description>&lt;p&gt;Django is a "batteries-included" framework that comes with built-in scaffolding provided by the &lt;code&gt;startproject&lt;/code&gt; command and the general concepts of apps. But, actually, Django provides incredible flexibility if desired around structure.&lt;/p&gt;

&lt;p&gt;Consider how to architect a single page website: what's the simplest way to do it? Off the top of my head, at least 5 approaches come to mind. Because if you really think about it, apps are not mandatory at all but rather an organizational convenience that help reason about the codebase. Ditto for independent views files. We don't even need templates for a simple site when we can return strings in a view. Pretty much a &lt;code&gt;urls.py&lt;/code&gt; file is the only "mandatory" part of the exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;On your command line, create a new directory called &lt;code&gt;five&lt;/code&gt; and make a new Django project. We'll be using Pipenv here and calling our top-level directory &lt;code&gt;config&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pipenv install django==3.0.7
$ pipenv shell
(five) $ django-admin startproject config .
(five) $ python manage.py migrate
(five) $ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate over to the Django homepage at &lt;a href="http://127.0.0.1:8000/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/&lt;/a&gt; to confirm everything is working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd7rdgl0c23okxkc30jji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd7rdgl0c23okxkc30jji.png" alt="Django Welcome Page" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. App + TemplateView
&lt;/h2&gt;

&lt;p&gt;The "traditional" approach would be to create a dedicated app for static pages and use &lt;a href="https://docs.djangoproject.com/en/3.0/ref/class-based-views/base/#templateview" rel="noopener noreferrer"&gt;TemplateView&lt;/a&gt;, a generic class-based view provided by Django.&lt;/p&gt;

&lt;p&gt;First, create the app which can be called whatever we choose. In this case, it'll be &lt;code&gt;pages&lt;/code&gt;. Make sure you've stopped the local server by typing &lt;code&gt;Control+c&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(five) $ python manage.py startapp pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the new app to our &lt;code&gt;INSTALLED_APPS&lt;/code&gt; setting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages.apps.PagesConfig&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;config/urls.py&lt;/code&gt; file by importing &lt;code&gt;include&lt;/code&gt; and adding a path for the pages app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, now what? We need a &lt;code&gt;urls.py&lt;/code&gt; file within our app, a views file, and a template file. &lt;/p&gt;

&lt;p&gt;Let's start with the &lt;code&gt;pages/urls.py&lt;/code&gt; file. First, create it by using the &lt;code&gt;touch&lt;/code&gt; command if you're on a Mac.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(five) $ touch pages/urls.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code includes a single path at &lt;code&gt;home1/&lt;/code&gt;, a &lt;code&gt;HomePageView&lt;/code&gt; we haven't created yet, and the URL name of &lt;code&gt;home&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view imports &lt;code&gt;TemplateView&lt;/code&gt; and points to a template called &lt;code&gt;home.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we need a template. Create a &lt;code&gt;templates&lt;/code&gt; directory within the &lt;code&gt;pages&lt;/code&gt; app, then a new directory called &lt;code&gt;pages&lt;/code&gt;, and finally the template called &lt;code&gt;home.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;home.html&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello World&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start up the local server again with &lt;code&gt;python manage.py runserver&lt;/code&gt; and navigate to &lt;a href="http://127.0.0.1:8000/home1/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/home1/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnawurr9xjsgoobf9lcoh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnawurr9xjsgoobf9lcoh.png" alt="1st Homepage" width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach works well for large projects where you want a dedicated &lt;code&gt;pages&lt;/code&gt; app for static pages. But since this article is for educational purposes, let's see 4 more ways we can achieve the same outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Function-Based View
&lt;/h2&gt;

&lt;p&gt;The first thing we could do is swap out the generic class-based  &lt;code&gt;TemplateView&lt;/code&gt; for a function-based view. Django allows for either using function-based or class-based views.&lt;/p&gt;

&lt;p&gt;We'll import &lt;code&gt;render&lt;/code&gt; at the top of &lt;code&gt;pages/views.py&lt;/code&gt; and add a new view, &lt;code&gt;home_page_view2&lt;/code&gt;, which requests the same template &lt;code&gt;home.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This new view will have its own path, &lt;code&gt;home2/&lt;/code&gt;, in the &lt;code&gt;pages/urls.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view2&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home2/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the server is still running and navigate to &lt;a href="http://127.0.0.1:8000/home2/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/home2/&lt;/a&gt; to see the new version of our homepage in action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv95nlszqswb4ret9l8vm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv95nlszqswb4ret9l8vm.png" alt="2nd Homepage" width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. No Template!
&lt;/h2&gt;

&lt;p&gt;A third approach would be to remove the template entirely, since all we're displaying is a short amount of text. We can create a new view that does this by importing &lt;code&gt;HttpResponse&lt;/code&gt; and hard-coding the string &lt;code&gt;Hello World&lt;/code&gt; to be returned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home_page_view3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello World&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view3&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home2/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home3/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to &lt;a href="http://127.0.0.1:8000/home3/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/home3/&lt;/a&gt; in your web browser to see this third version in action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnztlykqfb93htp5joxdl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnztlykqfb93htp5joxdl.png" alt="3rd Homepage" width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. No App!
&lt;/h2&gt;

&lt;p&gt;We don't have to use apps, actually, if we don't want to. It's possible to create a "Hello World" page solely within our  project-level &lt;code&gt;config&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Here is all the code we need in just a single file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home4/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've imported &lt;code&gt;TemplateView&lt;/code&gt; at the top, create a new URL path, pointed to the existed template within the path, and provided an updated URL name of &lt;code&gt;home4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="http://127.0.0.1:8000/home4/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/home4/&lt;/a&gt; to see this in action!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7b8u5rlpfcz3nk6lv4gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7b8u5rlpfcz3nk6lv4gh.png" alt="4th Homepage" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Models
&lt;/h2&gt;

&lt;p&gt;A fifth approach would be to use the database to display our "Hello World" message. That involves adding a &lt;code&gt;models.py&lt;/code&gt; file and leveraging the built-in &lt;a href="https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-display/#detailview" rel="noopener noreferrer"&gt;DetailView&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;pages/models.py&lt;/code&gt;. We'll only add a single field, &lt;code&gt;text&lt;/code&gt;, to the model called &lt;code&gt;Post&lt;/code&gt;. Adding an optional &lt;code&gt;__str__&lt;/code&gt; method means the model will display nicely within the Django admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we made a change to our database models, it's time for a migrations file and then to invoke &lt;code&gt;migrate&lt;/code&gt; to update the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(five) $ python manage.py makemigrations
(five) $ python manage.py migrate 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll want a basic &lt;code&gt;pages/admin.py&lt;/code&gt; file to see our &lt;code&gt;pages&lt;/code&gt; app within the admin so update &lt;code&gt;pages/admin.py&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/admin.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a superuser account and start up the server again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(five) $ python manage.py createsuperuser
(five) $ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log in to the admin at &lt;a href="http://127.0.0.1:8000/admin/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin/&lt;/a&gt; and click on the link for &lt;code&gt;Posts&lt;/code&gt; under &lt;code&gt;Pages&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvtq7ya1tehnzr9i5ys7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvtq7ya1tehnzr9i5ys7z.png" alt="Admin" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on "+Add" next to it. Add our text which will be "Hello World" and click the "Save" button in the bottom right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftyfcu3o77ri0c7l5fisb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftyfcu3o77ri0c7l5fisb.png" alt="Add" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Success! We've added our first post to the database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgj5wugmhzfo5zz17gayt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgj5wugmhzfo5zz17gayt.png" alt="Admin Post" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To display this post we'll need a urls file, a views file, and a template file. Start with &lt;code&gt;pages/urls.py&lt;/code&gt; where we'll import a new view called &lt;code&gt;HomeDetailView&lt;/code&gt; and add a path for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomeDetailView&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home2/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home3/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home_page_view3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home/&amp;lt;int:pk&amp;gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomeDetailView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;post_detail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pages/views.py&lt;/code&gt; file needs to import &lt;code&gt;DetailView&lt;/code&gt; at the top and our &lt;code&gt;Post&lt;/code&gt; model. Then create &lt;code&gt;HomeDetailView&lt;/code&gt; which references our &lt;code&gt;Post&lt;/code&gt; model and a new &lt;code&gt;pages/home5.html&lt;/code&gt; template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt; 
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DetailView&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home_page_view2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages/home.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home_page_view3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, World!)


class HomeDetailView(DetailView): # new
    model = Post
    template_name = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to create a new template called &lt;code&gt;home5.html&lt;/code&gt; that will display the &lt;code&gt;text&lt;/code&gt; field from our model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- pages/templates/home5.html --&amp;gt;&lt;/span&gt;
{{ post.text }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the local server is running &lt;code&gt;python manage.py runserver&lt;/code&gt; and navigate to &lt;a href="http://127.0.0.1:8000/home/1/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/home/1/&lt;/a&gt; to see our 5th approach in action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fy0qgly015bsr0luogsi5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fy0qgly015bsr0luogsi5.png" alt="5th Homepage" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And there it is!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you understand these five approaches you'll be well on your way to understanding all that Django has to offer. And you can start exploring additional ways to tackle this arbitrary challenge, for example, taking a look at &lt;a href="https://docs.djangoproject.com/en/3.0/ref/class-based-views/base/#view" rel="noopener noreferrer"&gt;base view class&lt;/a&gt;, &lt;a href="https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-display/#adding-extra-context" rel="noopener noreferrer"&gt;get_context_data&lt;/a&gt;,  &lt;a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest" rel="noopener noreferrer"&gt;HTTPRequest&lt;/a&gt; and likely many others.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
