<?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>DEV Community: Joshua Masiko</title>
    <description>The latest articles on DEV Community by Joshua Masiko (@joshwizzy).</description>
    <link>https://dev.to/joshwizzy</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%2Fuser%2Fprofile_image%2F350165%2Ff3833efe-b951-4980-8b0d-66408699f111.png</url>
      <title>DEV Community: Joshua Masiko</title>
      <link>https://dev.to/joshwizzy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joshwizzy"/>
    <language>en</language>
    <item>
      <title>How to deploy a Django app to Google Cloud Run using Terraform</title>
      <dc:creator>Joshua Masiko</dc:creator>
      <pubDate>Mon, 01 Jan 2024 18:45:42 +0000</pubDate>
      <link>https://dev.to/joshwizzy/how-to-deploy-a-django-app-to-google-cloud-run-using-terraform-3al9</link>
      <guid>https://dev.to/joshwizzy/how-to-deploy-a-django-app-to-google-cloud-run-using-terraform-3al9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Deploying a Django application to a production environment may require you to perform the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provision hardware or virtual machines to run the application and database server&lt;/li&gt;
&lt;li&gt;Install the required software on the provisioned servers&lt;/li&gt;
&lt;li&gt;Purchase a domain from a registar for and setup the relevant DNS records to associate the domain with your app&lt;/li&gt;
&lt;li&gt;Setup a webserver to terminate HTTP(S) traffic to your application&lt;/li&gt;
&lt;li&gt;Obtain and congiure SSL certificate to secure traffic to the application&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Goole Cloud Run
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/run"&gt;Cloud Run&lt;/a&gt; is a managed platform that enables you to run container based workloads on top of Google infrastructure.&lt;br&gt;&lt;br&gt;
Cloud Run automates many of the above steps and allows you to focus on developing and deploying updates to your application.&lt;/p&gt;

&lt;p&gt;Some of the benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run code written in any programming language on cloud run if you can package into a container&lt;/li&gt;
&lt;li&gt;Source based deployments using build packs for supported languages making an explicit container packaging step optional.&lt;/li&gt;
&lt;li&gt;Support for &lt;strong&gt;services&lt;/strong&gt; which respond to web requests, and one-off &lt;strong&gt;jobs&lt;/strong&gt; which run and exit after doing some work&lt;/li&gt;
&lt;li&gt;A generous free tier and pay-per-use based pricing.&lt;/li&gt;
&lt;li&gt;Services automatically receive unique https endpoints on a &lt;code&gt;run.app&lt;/code&gt; sub domain with support for custom domains&lt;/li&gt;
&lt;li&gt;Auto-scaling which automatically adds and removes running container instances in response to web traffic or resource utilization&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;You have 3 options when installing an application on Google Cloud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using the web based console&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;gcloud&lt;/code&gt; tool&lt;/li&gt;
&lt;li&gt;Using an automated provisioning tool such as Terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial uses the &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; tool to create a repeatable recipe for deploying a Django application to Google Cloud Run and provisioning the backing services.&lt;/p&gt;
&lt;h2&gt;
  
  
  Google Cloud Components
&lt;/h2&gt;

&lt;p&gt;You will use the following Google Cloud components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/run"&gt;Cloud Run&lt;/a&gt;: managed compute platform to run container based &lt;strong&gt;services&lt;/strong&gt; which respond to web requests or &lt;strong&gt;jobs&lt;/strong&gt; which run to completion&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/artifact-registry"&gt;Artifact Registry&lt;/a&gt;: artifact storage to manage container images.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/sql"&gt;Cloud SQL&lt;/a&gt;: managed relational database service for MySQL, PostgreSQL, and SQL Server.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/storage"&gt;Cloud Storage&lt;/a&gt;: blog storage for static assets and media files&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/secret-manager"&gt;Secret Manager&lt;/a&gt;: secure storage for sensitive data e.g passwords.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 0 - Installing the supporting tools
&lt;/h2&gt;

&lt;p&gt;Install the Google Cloud CLI by following the intructions &lt;a href="https://cloud.google.com/sdk/docs/install"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Initialize the gcloud CLI by run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Terraform by following the intructions &lt;a href="https://developer.hashicorp.com/terraform/install"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Creating a Google Cloud project
&lt;/h2&gt;

&lt;p&gt;Create a new Google Cloud project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud projects create &lt;span class="nt"&gt;--name&lt;/span&gt; django-on-cloud-run &lt;span class="nt"&gt;--set-as-default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt; at the prompt and press &lt;code&gt;enter&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;No project id provided.

Use [django-on-cloud-run-409907] as project id (Y/n)?  y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://cloud.google.com/billing/docs/how-to/modify-project"&gt;Enable billing&lt;/a&gt; on the project to allow usage of the required Google Cloud APIs within the project.&lt;/p&gt;

&lt;p&gt;Set an environment variable for the generated project ID by running the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROJECT_ID=$(gcloud config get-value project)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obtain credentials to enable Terraform to make authenticated requests to Google Cloud&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth application-default login &lt;span class="nt"&gt;--project&lt;/span&gt; &lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 - Creating a starter Django project
&lt;/h2&gt;

&lt;p&gt;Create a directory to host the project source code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir django-on-cloudrun &amp;amp;&amp;amp; cd django-on-cloudrun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create 2 subfolders for the Django application code and the Terraform infrastructure code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir {djangocloudrun,terraform}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;djangocloudrun&lt;/code&gt; subfolder will contain the Django/Python code and the &lt;code&gt;terraform&lt;/code&gt; folder will contain the infrastructure provisioning code.&lt;/p&gt;

&lt;p&gt;Create a python virtual enviroment to isolate the project dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m venv venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate the 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;source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the &lt;code&gt;djangocloudrun&lt;/code&gt; subfolder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd djangocloudrun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;requirements.txt&lt;/code&gt; file with the following content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# django-on-cloudrun/djangocloudrun/requirements.txt&lt;/span&gt;
&lt;span class="nv"&gt;Django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;5.0
django-environ&lt;span class="o"&gt;==&lt;/span&gt;0.10.0
django-storages[google]&lt;span class="o"&gt;==&lt;/span&gt;1.13.2
google-cloud-run&lt;span class="o"&gt;==&lt;/span&gt;0.10.1
&lt;span class="nv"&gt;gunicorn&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;20.1.0
psycopg2-binary&lt;span class="o"&gt;==&lt;/span&gt;2.9.9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;django-environ&lt;/code&gt;: used to read application settings from the environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;django-storages[google]&lt;/code&gt;: used to integrate with Google Cloud Storage for static assets management&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google-cloud-run&lt;/code&gt;: used to read service metadata on startup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gunicorn&lt;/code&gt;: WSGI application server for the Django application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;psycopg2-binary&lt;/code&gt;: used to communicate with the managed Cloud SQL PostgreSQL instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the project dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the dependencies are installed create a starter Django project using the &lt;code&gt;django-admin&lt;/code&gt; tool&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;django-admin startproject djangocloudrun &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View your project structure by running this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You project structure should look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── djangocloudrun
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rename the default generated &lt;code&gt;settings.py&lt;/code&gt; file to &lt;code&gt;basesettings.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;djangocloudrun/settings.py djangocloudrun/basesettings.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new &lt;code&gt;settings.py&lt;/code&gt; file with the following content&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-on-cloudrun/djangocloudrun/djangocloudrun/settings.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google.auth&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.cloud.run_v2.services.services.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ServicesClient&lt;/span&gt;


&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.basesettings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;


&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APPLICATION_SETTINGS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&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;environ&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;APPLICATION_SETTINGS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;env_file&lt;/span&gt; &lt;span class="o"&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;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&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;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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;db&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="n"&gt;SERVICE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SERVICE_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;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROJECT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultCredentialsError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;PROJECT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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;http://metadata.google.internal/computeMetadata/v1/instance/region&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&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;Metadata-Flavor&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;Google&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;REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;service_path&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;projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/locations/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/services/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ServicesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;service_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_service&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="n"&gt;service_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;uri&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="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_uri&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;CSRF_TRUSTED_ORIGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;service_uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&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;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;GS_BUCKET_NAME&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&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_BUCKET_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;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&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;GS_DEFAULT_ACL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;publicRead&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;STORAGES&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;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;storages.backends.gcloud.GoogleCloudStorage&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;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="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;storages.backends.gcloud.GoogleCloudStorage&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;Create a &lt;code&gt;.env&lt;/code&gt; file in the Django project root with the following content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# django-on-cloudrun/djangocloudrun/.env&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"django-insecure-secret-key"&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite:////tmp/db.sqlite3
&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;Procfile&lt;/code&gt; file in the Django project root with the following content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# django-on-cloudrun/djangocloudrun/Procfile&lt;/span&gt;
web: gunicorn &lt;span class="nt"&gt;--bind&lt;/span&gt; 0.0.0.0:&lt;span class="nv"&gt;$PORT&lt;/span&gt; &lt;span class="nt"&gt;--workers&lt;/span&gt; 1 &lt;span class="nt"&gt;--threads&lt;/span&gt; 8 &lt;span class="nt"&gt;--timeout&lt;/span&gt; 0 djangocloudrun.wsgi:application
migrate_collectstatic: python manage.py migrate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python manage.py collectstatic &lt;span class="nt"&gt;--noinput&lt;/span&gt; &lt;span class="nt"&gt;--clear&lt;/span&gt;
create_superuser: python manage.py createsuperuser &lt;span class="nt"&gt;--username&lt;/span&gt; admin &lt;span class="nt"&gt;--email&lt;/span&gt; noop@example.com &lt;span class="nt"&gt;--noinput&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Procfile lists the web application entry points that are executed by Cloud Run service and jobs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;web&lt;/code&gt; default entry point runs the application&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;migrate_collecstatic&lt;/code&gt; entry point is executed by a job to run database migrations and copy static files to Cloud Storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;create_superuser&lt;/code&gt; entry point is executed by a job to create a Django superuser&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3 - Running the application locally
&lt;/h2&gt;

&lt;p&gt;Initialize the sqlite database by running migrations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to this if migrations are run successfully&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a superuser account for logging into the Django admin interface&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py createsuperuser &lt;span class="nt"&gt;--username&lt;/span&gt; admin &lt;span class="nt"&gt;--email&lt;/span&gt; admin@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted enter a password of your choice and confirm&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Password:
Password &lt;span class="o"&gt;(&lt;/span&gt;again&lt;span class="o"&gt;)&lt;/span&gt;:
Superuser created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the developement server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server starts up and prints output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Watching &lt;span class="k"&gt;for &lt;/span&gt;file changes with StatReloader
Performing system checks...

System check identified no issues &lt;span class="o"&gt;(&lt;/span&gt;0 silenced&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
January 01, 2024 - 08:32:11
Django version 5.0, using settings &lt;span class="s1"&gt;'django_cloudrun.settings'&lt;/span&gt;
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to &lt;a href="http://127.0.0.1:8000"&gt;http://127.0.0.1:8000&lt;/a&gt; in your web browser and you will see the Django welcome page&lt;/p&gt;

&lt;p&gt;&lt;a href="/success.png" class="article-body-image-wrapper"&gt;&lt;img src="/success.png" alt="Success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the Django admin site &lt;code&gt;http://127.0.0.1:8000/admin&lt;/code&gt; and login using the &lt;code&gt;admin&lt;/code&gt; username and the superuser password you set.&lt;/p&gt;

&lt;p&gt;&lt;a href="/admin_login.png" class="article-body-image-wrapper"&gt;&lt;img src="/admin_login.png" alt="Django admin login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Django admin dashboard is displayed&lt;/p&gt;

&lt;p&gt;&lt;a href="/admin_dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="/admin_dashboard.png" alt="Success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - Deploying the app to Cloud Run using Terraform
&lt;/h2&gt;

&lt;p&gt;Create a Cloud Storage bucket to store Terraform state using the &lt;code&gt;gsutil&lt;/code&gt; tool which is bundled with the &lt;code&gt;gcloud&lt;/code&gt; installation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gsutil mb gs://&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;-tfstate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable Object Versioning on the storage bucket to keep a history of Terraform state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gsutil versioning &lt;span class="nb"&gt;set &lt;/span&gt;on gs://&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;-tfstate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the &lt;code&gt;terraform&lt;/code&gt; subfolder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;variables.tf&lt;/code&gt; file with the following content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django-on-cloudrun/terraform/variables.tf&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"project"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Google Cloud Project ID"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Google Cloud Region"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"service_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of Cloud Run service"&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 &lt;code&gt;terraform.tfvars&lt;/code&gt; file with the following content.&lt;br&gt;
Replace the &lt;code&gt;PROJECT_ID&lt;/code&gt; placeholder with the value of the &lt;code&gt;PROJECT_ID&lt;/code&gt; environment variable you set earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django-on-cloudrun/terraform/terraform.tfvars&lt;/span&gt;
&lt;span class="nx"&gt;project&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PROJECT_ID"&lt;/span&gt;
&lt;span class="nx"&gt;service_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django-on-cloudrun"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;templates&lt;/code&gt; subfolder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crete a &lt;code&gt;application_settings.tftpl&lt;/code&gt; within the &lt;code&gt;templates&lt;/code&gt; subfolder with the following content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django-on-cloudrun/terraform/templates/application_settings.tftpl&lt;/span&gt;
&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgres://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@//cloudsql/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;STATICFILES_BUCKET_NAME&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;staticfiles_bucket&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;secret_key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;main.tf&lt;/code&gt; file with the following content.&lt;br&gt;
Replace the &lt;code&gt;PROJECT_ID&lt;/code&gt; placeholder with the value of the &lt;code&gt;PROJECT_ID&lt;/code&gt; environment variable you set earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django-on-cloudrun/terraform/main.tf&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.9.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.6.0"&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="c1"&gt;# Store Terraform state in a Google Cloud Storage bucket&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PROJECT_ID-tfstate"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"google"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Enable the required Google Cloud APIs&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_project_service"&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s2"&gt;"artifactregistry.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"cloudbuild.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"compute.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"run.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"secretmanager.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"sql-component.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"sqladmin.googleapis.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
  &lt;span class="nx"&gt;disable_on_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a service account for the Cloud Run service&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_service_account"&lt;/span&gt; &lt;span class="s2"&gt;"django_cloudrun"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;account_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django-run-sa"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a Artifact Repository to store the application image&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_artifact_registry_repository"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;format&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DOCKER"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
  &lt;span class="nx"&gt;repository_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django-app"&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;google_project_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Provision a database server instance for the application&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_sql_database_instance"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django"&lt;/span&gt;
  &lt;span class="nx"&gt;database_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"POSTGRES_14"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db-f1-micro"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;deletion_protection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;google_project_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a database within the instance&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_sql_database"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django"&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_sql_database_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a random password for the app database user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create the Django application database user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_sql_user"&lt;/span&gt; &lt;span class="s2"&gt;"django"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django"&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_sql_database_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Define local variables&lt;/span&gt;
&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;django_cloudrun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;repository_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_artifact_registry_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repository_id&lt;/span&gt;
  &lt;span class="nx"&gt;ar_repository&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-docker.pkg.dev/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repository_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ar_repository&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:bootstrap"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Assign the Cloud Run service the required roles to connect to the DB and fetch service metadata&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_project_iam_member"&lt;/span&gt; &lt;span class="s2"&gt;"service_roles"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s2"&gt;"cloudsql.client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"run.viewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;member&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_account&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a Cloud Storage bucket to store static files&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_storage_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"staticfiles"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-staticfiles"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"US"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Grant the Cloud Run service account admin access to the staticfiles bucket&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_storage_bucket_iam_binding"&lt;/span&gt; &lt;span class="s2"&gt;"staticfiles_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;staticfiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/storage.admin"&lt;/span&gt;
  &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_account&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a random string to use as the Django secret key&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"django_secret_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;length&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;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_secret_manager_secret"&lt;/span&gt; &lt;span class="s2"&gt;"application_settings"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application_settings"&lt;/span&gt;

  &lt;span class="nx"&gt;replication&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;auto&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;google_project_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Replace the Terraform template variables and save the rendered content as a secret&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_secret_manager_secret_version"&lt;/span&gt; &lt;span class="s2"&gt;"application_settings"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;secret_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/templates/application_settings.tftpl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;staticfiles_bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;staticfiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="c1"&gt;# media_bucket       = google_storage_bucket.media.name&lt;/span&gt;
    &lt;span class="nx"&gt;secret_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;django_secret_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_sql_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;django&lt;/span&gt;
    &lt;span class="nx"&gt;instance&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_sql_database_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;
    &lt;span class="nx"&gt;database&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_sql_database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Grant the Cloud Run service account access to the application settings secret&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_secret_manager_secret_iam_binding"&lt;/span&gt; &lt;span class="s2"&gt;"application_settings"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
  &lt;span class="nx"&gt;members&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_account&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Generate a random password for the superuser&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"superuser_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Save the superuser password as a secret&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_secret_manager_secret"&lt;/span&gt; &lt;span class="s2"&gt;"superuser_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"superuser_password"&lt;/span&gt;
  &lt;span class="nx"&gt;replication&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;auto&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;google_project_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_secret_manager_secret_version"&lt;/span&gt; &lt;span class="s2"&gt;"superuser_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;superuser_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;secret_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;superuser_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Grant the Cloud Run service account access to the superuser password secret&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_secret_manager_secret_iam_binding"&lt;/span&gt; &lt;span class="s2"&gt;"superuser_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;superuser_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
  &lt;span class="nx"&gt;members&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_account&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Build the application image that the Cloud Run service and jobs will use&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"terraform_data"&lt;/span&gt; &lt;span class="s2"&gt;"bootstrap"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;working_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/../djangocloudrun"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcloud builds submit --pack image=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;google_artifact_registry_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;google_project_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create the migrate_collectstatic Cloud Run job&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_v2_job"&lt;/span&gt; &lt;span class="s2"&gt;"migrate_collectstatic"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"migrate-collectstatic"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;django_cloudrun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;

      &lt;span class="nx"&gt;volumes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudsql"&lt;/span&gt;
        &lt;span class="nx"&gt;cloud_sql_instance&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;instances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;google_sql_database_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection_name&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="nx"&gt;containers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"migrate_collectstatic"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"APPLICATION_SETTINGS"&lt;/span&gt;
          &lt;span class="nx"&gt;value_source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;secret_key_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;
              &lt;span class="nx"&gt;secret&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&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="nx"&gt;volume_mounts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudsql"&lt;/span&gt;
          &lt;span class="nx"&gt;mount_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/cloudsql"&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;terraform_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bootstrap&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="c1"&gt;# Create the create_superuser Cloud Run job&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_v2_job"&lt;/span&gt; &lt;span class="s2"&gt;"create_superuser"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"create-superuser"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;django_cloudrun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;

      &lt;span class="nx"&gt;volumes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudsql"&lt;/span&gt;
        &lt;span class="nx"&gt;cloud_sql_instance&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;instances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;google_sql_database_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection_name&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="nx"&gt;containers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"create_superuser"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"APPLICATION_SETTINGS"&lt;/span&gt;
          &lt;span class="nx"&gt;value_source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;secret_key_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;
              &lt;span class="nx"&gt;secret&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&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="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DJANGO_SUPERUSER_PASSWORD"&lt;/span&gt;
          &lt;span class="nx"&gt;value_source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;secret_key_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;superuser_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;
              &lt;span class="nx"&gt;secret&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;superuser_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&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="nx"&gt;volume_mounts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudsql"&lt;/span&gt;
          &lt;span class="nx"&gt;mount_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/cloudsql"&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;terraform_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bootstrap&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Run the migrate_collectstatic the Cloud Run job&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"terraform_data"&lt;/span&gt; &lt;span class="s2"&gt;"execute_migrate_collectstatic"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcloud run jobs execute migrate-collectstatic --region &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --wait"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;google_cloud_run_v2_job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;migrate_collectstatic&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="c1"&gt;# Run the create_superuser the Cloud Run job&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"terraform_data"&lt;/span&gt; &lt;span class="s2"&gt;"execute_create_superuser"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcloud run jobs execute create-superuser --region &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --wait"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;google_cloud_run_v2_job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create_superuser&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="c1"&gt;# Create and deploy the Cloud Run service&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_service"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;autogenerate_revision_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;terraform_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;service_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;django_cloudrun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
      &lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;

        &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SERVICE_NAME"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"APPLICATION_SETTINGS"&lt;/span&gt;
          &lt;span class="nx"&gt;value_from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;secret_key_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;key&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;
              &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret_id&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;annotations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"autoscaling.knative.dev/maxScale"&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
        &lt;span class="s2"&gt;"run.googleapis.com/cloudsql-instances"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_sql_database_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection_name&lt;/span&gt;
        &lt;span class="s2"&gt;"run.googleapis.com/client-name"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&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="nx"&gt;traffic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;percent&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="nx"&gt;latest_revision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;terraform_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute_migrate_collectstatic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;terraform_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute_create_superuser&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="c1"&gt;# Grant permission to unauthenticated users to invoke the Cloud Run service&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"google_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"noauth"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;role&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/run.invoker"&lt;/span&gt;
    &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"allUsers"&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_service_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"noauth"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_cloud_run_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_cloud_run_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_cloud_run_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;

  &lt;span class="nx"&gt;policy_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;google_iam_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;policy_data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Print the Cloud Run service url&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"service_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_cloud_run_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize Terraform by running the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see printed output similar to this on successfuly initialization&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initializing the backend...

Successfully configured the backend "gcs"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- terraform.io/builtin/terraform is built in to Terraform
- Finding hashicorp/google versions matching "~&amp;gt; 5.9.0"...
- Finding hashicorp/random versions matching "~&amp;gt; 3.6.0"...
- Installing hashicorp/google v5.9.0...
- Installed hashicorp/google v5.9.0 (signed by HashiCorp)
- Installing hashicorp/random v3.6.0...
- Installed hashicorp/random v3.6.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;terraform plan&lt;/code&gt; to review the resource terraform will create in Google Cloud&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan will display the number of resources that will be added&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Plan: 32 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + service_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;terraform apply&lt;/code&gt; to create the resources in Google Cloud and deploy the Cloud Run service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; at the prompt and press &lt;code&gt;enter:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Plan: 32 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + service_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class="s1"&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process will take a while to create all the resources and display output similar to this on completion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 32 added, 0 changed, 0 destroyed.

Outputs:

service_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://django-on-cloudrun-qfes6ko4lq-uc.a.run.app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the printed value of &lt;code&gt;service_url&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 - Logging into the application
&lt;/h2&gt;

&lt;p&gt;Visit the service url in your browser and you will see the Django welcome page&lt;/p&gt;

&lt;p&gt;&lt;a href="/cloudrun_success.png" class="article-body-image-wrapper"&gt;&lt;img src="/cloudrun_success.png" alt="Success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the Django Admin login page by appending &lt;code&gt;/admin&lt;/code&gt; to your service URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://django-on-cloudrun-qfes6ko4lq-uc.a.run.app/admin"&gt;https://django-on-cloudrun-qfes6ko4lq-uc.a.run.app/admin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Retrieve the superuser password using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud secrets versions access latest &lt;span class="nt"&gt;--secret&lt;/span&gt; superuser_password &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login with the username &lt;code&gt;admin&lt;/code&gt; and the retrieved password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 - Disabling DEBUG mode
&lt;/h2&gt;

&lt;p&gt;Modify the &lt;code&gt;templates/application_settings.tftpl&lt;/code&gt; file and change the &lt;code&gt;DEBUG&lt;/code&gt; value from &lt;code&gt;on&lt;/code&gt; to &lt;code&gt;off&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The updated file should look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgres://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@//cloudsql/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;STATICFILES_BUCKET_NAME&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;staticfiles_bucket&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;secret_key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;teraform plan&lt;/code&gt; to review the changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will display a plan of the actions to make the desired change&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;...

  ~ update &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-place&lt;/span&gt;
-/+ destroy and &lt;span class="k"&gt;then &lt;/span&gt;create replacement

Terraform will perform the following actions:

....

Plan: 1 to add, 3 to change, 1 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;teraform apply&lt;/code&gt; to perform the desired changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; at the prompt and press &lt;code&gt;enter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;...

Plan: 1 to add, 3 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class="s1"&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will update the secret and redeploy the service then print the service url on completion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 1 added, 3 changed, 1 destroyed.

Outputs:

service_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://django-on-cloudrun-qfes6ko4lq-uc.a.run.app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit the service URL and confirm that the welcome page is not displayed anymore when &lt;code&gt;DEBUG&lt;/code&gt; mode is disabled.&lt;br&gt;
&lt;a href="/debug_off.png" class="article-body-image-wrapper"&gt;&lt;img src="/debug_off.png" alt="Success"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 7 - Cleaning Up
&lt;/h2&gt;

&lt;p&gt;In order not incur unnecessary cost you can delete the project.&lt;/p&gt;

&lt;p&gt;This action will delete all the resources you created in the previous steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud projects delete &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt; at the prompt and press &lt;code&gt;enter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Your project will be deleted.

Do you want to &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt;?  y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the project is deleted you will see a message like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Deleted &lt;span class="o"&gt;[&lt;/span&gt;https://cloudresourcemanager.googleapis.com/v1/projects/django-on-cloud-run-409909].
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/joshwizzy/django-on-cloudrun"&gt;The Source code for this article can be found here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>cloudrun</category>
      <category>terraform</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Google Cloud Run Service-to-Service Authentication using Go</title>
      <dc:creator>Joshua Masiko</dc:creator>
      <pubDate>Sat, 06 May 2023 12:34:17 +0000</pubDate>
      <link>https://dev.to/joshwizzy/google-cloud-run-service-to-service-authentication-using-go-34ol</link>
      <guid>https://dev.to/joshwizzy/google-cloud-run-service-to-service-authentication-using-go-34ol</guid>
      <description>&lt;p&gt;In this tutorial, we will demonstrate how to set up service-to-service authentication for Google Cloud Run services using the Go programming language. &lt;/p&gt;

&lt;p&gt;We will cover the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy two services - a sender and a receiver. Initially, the receiver will not require any authentication. &lt;/li&gt;
&lt;li&gt;Add authentication to the receiver and demonstrate that the sender's requests will fail. &lt;/li&gt;
&lt;li&gt;Authorize the sender identity to the receive and demonstrate that the sender's requests will  succeed&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you can begin this tutorial, you will need the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Google Cloud Platform (GCP) account and a new project&lt;/li&gt;
&lt;li&gt;Enable billing for the new project&lt;/li&gt;
&lt;li&gt;Install and configure the Google Cloud SDK (gcloud) on your local machine&lt;/li&gt;
&lt;li&gt;Enable the required APIs&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a new project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://console.cloud.google.com/"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click the project drop-down and select or create the project you want to use for this tutorial.&lt;/li&gt;
&lt;li&gt;Take note of your project ID, as you will need it later.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Enable billing
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.cloud.google.com/"&gt;Cloud Console&lt;/a&gt;, open the main menu and select "Billing".&lt;/li&gt;
&lt;li&gt;Click "Link a billing account" and follow the steps to set up billing for your project.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Install and configure the Google Cloud SDK (gcloud)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Download and install the &lt;a href="https://cloud.google.com/sdk/docs/install"&gt;Google Cloud SDK&lt;/a&gt; for your operating system.&lt;/li&gt;
&lt;li&gt;Authenticate to your Google Cloud account using the gcloud tool:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will open a new browser window, asking you to log in with your Google account and grant permission to access your GCP resources.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up the default project and region:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud config set project &amp;lt;YOUR_PROJECT_ID&amp;gt;
$ gcloud config set compute/region &amp;lt;YOUR_REGION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accept to enable the &lt;code&gt;compute.googleapis.com&lt;/code&gt; API on the project if prompted.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_PROJECT_ID&amp;gt;&lt;/code&gt; with the project ID you noted earlier and &lt;code&gt;&amp;lt;YOUR_REGION&amp;gt;&lt;/code&gt; with a desired region, such as &lt;code&gt;us-central1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set the &lt;code&gt;PROJECT_ID&lt;/code&gt; and &lt;code&gt;REGION&lt;/code&gt; variables using the currently configured project and region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ PROJECT_ID=$(gcloud config get-value project)
$ REGION=$(gcloud config get-value compute/region)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable the required APIs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.cloud.google.com/"&gt;Cloud Console&lt;/a&gt;, open the main menu and select "APIs &amp;amp; Services" &amp;gt; "Library".&lt;/li&gt;
&lt;li&gt;Search for "Cloud Run" and click on "Cloud Run API".&lt;/li&gt;
&lt;li&gt;Click "Enable" to enable the Cloud Run API for your project.&lt;/li&gt;
&lt;li&gt;Repeat the process for "Cloud Build API" and "Identity Token Service API".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also enable the required APIs using the &lt;code&gt;gcloud&lt;/code&gt; command-line tool by running the following commands:&lt;/p&gt;

&lt;p&gt;Enable the Cloud Run API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud services enable run.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the Cloud Build API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud services enable cloudbuild.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the Identity Token Service API (also known as the IAM Service Account Credentials API):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud services enable iamcredentials.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands will enable the respective APIs for your current GCP project. &lt;/p&gt;

&lt;p&gt;Each of the three APIs serves a specific purpose, and enabling them ensures that the required services are accessible in your project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Run API&lt;/strong&gt;: The Cloud Run API is necessary for deploying, managing, and scaling your containerized applications on Google Cloud Run. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Build API&lt;/strong&gt;: The Cloud Build API allows you to build, test, and deploy your source code on Google Cloud. In this tutorial, you use the &lt;code&gt;gcloud builds submit&lt;/code&gt; command to build your Docker images and push them to Google Container Registry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity Token Service API&lt;/strong&gt; (IAM Service Account Credentials API): This API enables you to generate access tokens, sign JSON Web Tokens (JWTs), and create OIDC ID tokens for service accounts. In the tutorial, you use service-to-service authentication, which requires generating identity tokens for your services. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now you're ready to proceed with the tutorial on service-to-service authentication for Google Cloud Run using Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy sending and receiving services
&lt;/h2&gt;

&lt;p&gt;Create a directory for the project source code&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ mkdir ${PROJECT_ID} &amp;amp;&amp;amp; cd ${PROJECT_ID}&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Receiving Service
&lt;/h3&gt;

&lt;p&gt;First, let's create the receiving service. Create a new directory named &lt;code&gt;receiving-service&lt;/code&gt; and navigate to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir receiving-service &amp;amp;&amp;amp; cd receiving-service &amp;amp;&amp;amp; go mod init receiver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file named &lt;code&gt;main.go&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello from the receiving service!")
  })

  http.ListenAndServe(":8080", nil)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; in the same directory with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Build stage
FROM golang:1.17 AS builder

# Set the working directory
WORKDIR /app

# Copy Go module files
COPY go.* ./

# Download dependencies
RUN go mod download

# Copy Go files
COPY main.go .

# Build the binary
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

# Final stage
FROM alpine:3.15

# Set the working directory
WORKDIR /app

# Copy the binary from the builder stage
COPY --from=builder /app/server /app/server

# Expose the listening port
EXPOSE 8080

# Run the server
CMD ["/app/server"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, build and push the Docker image to Google Container Registry (GCR):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud builds submit --tag gcr.io/${PROJECT_ID}/receiving-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the build is complete, deploy the service to Cloud Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud run deploy receiving-service --image gcr.io/${PROJECT_ID}/receiving-service --region ${REGION} --platform managed --allow-unauthenticated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the &lt;code&gt;RECEIVING_SERVICE_URL&lt;/code&gt; variable to the value of the receiving service URL after deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ RECEIVING_SERVICE_URL=$(gcloud run services describe receiving-service --region $REGION --format='value(status.url)')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make a request to the receiving service URL and confirm that it returns the expected response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl ${RECEIVING_SERVICE_URL}
Hello from the receiving service!                
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending Service
&lt;/h3&gt;

&lt;p&gt;Create a new directory named &lt;code&gt;sending-service&lt;/code&gt; and navigate to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ../ &amp;amp;&amp;amp; mkdir sending-service &amp;amp;&amp;amp; cd sending-service &amp;amp;&amp;amp; go mod init sender
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file named &lt;code&gt;main.go&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
  "fmt"
  "io/ioutil"
  "log"
  "net/http"
  "os"
)

func main() {
  receivingServiceURL := os.Getenv("RECEIVING_SERVICE_URL")
  if receivingServiceURL == "" {
    log.Fatal("RECEIVING_SERVICE_URL environment variable is not set")
  }

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    resp, err := http.Get(receivingServiceURL)
    if err != nil {
      log.Printf("Failed to make request: %v", err)
      http.Error(w, "Failed to make request", http.StatusInternalServerError)
      return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
      log.Printf("Failed to read response body: %v", err)
      http.Error(w, "Failed to read response body", http.StatusInternalServerError)
      return
    }

    fmt.Fprintf(w, "Response from receiving service: %s", string(body))
  })

  http.ListenAndServe(":8080", nil)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; in the same directory with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Build stage
FROM golang:1.17 AS builder

# Set the working directory
WORKDIR /app

# Copy Go module files
COPY go.* ./

# Download dependencies
RUN go mod download

# Copy Go files
COPY main.go .

# Build the binary
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

# Final stage
FROM alpine:3.15

# Install CA certificates for HTTPS calls
RUN apk --no-cache add ca-certificates

# Set the working directory
WORKDIR /app

# Copy the binary from the builder stage
COPY --from=builder /app/server /app/server

# Expose the listening port
EXPOSE 8080

# Run the server
CMD ["/app/server"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, build and push the Docker image to Google Container Registry (GCR):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud builds submit --tag gcr.io/${PROJECT_ID}/sending-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the build is complete, deploy the service to Cloud Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud run deploy sending-service --image gcr.io/${PROJECT_ID}/sending-service --region ${REGION} --platform managed --allow-unauthenticated --set-env-vars RECEIVING_SERVICE_URL=${RECEIVING_SERVICE_URL}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the &lt;code&gt;SENDING_SERVICE_URL&lt;/code&gt; variable to the value of the sending service URL after deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ SENDING_SERVICE_URL=$(gcloud run services describe sending-service --region $REGION --format='value(status.url)')`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make a request to the receiving service URL and confirm that it returns the expected response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl ${RECEIVING_SERVICE_URL}
Hello from the receiving service!                
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit the sending service URL in your browser, and you should see the message from the receiving service, indicating that the call to the receiving service succeeded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl ${SENDING_SERVICE_URL}
Response from receiving service: Hello from the receiving service!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update the receiving service
&lt;/h3&gt;

&lt;p&gt;First, set the &lt;code&gt;--no-allow-unauthenticated&lt;/code&gt; flag while deploying the receiving service to require authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud run deploy receiving-service --image gcr.io/${PROJECT_ID}/receiving-service --region ${REGION} --platform managed --no-allow-unauthenticated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit the sending service URL in your browser, and you should see an error message, indicating that the call to the receiving service failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl ${SENDING_SERVICE_URL}
Response from receiving service: 
&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv="content-type" content="text/html;charset=utf-8"&amp;gt;
&amp;lt;title&amp;gt;403 Forbidden&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body text=#000000 bgcolor=#ffffff&amp;gt;
&amp;lt;h1&amp;gt;Error: Forbidden&amp;lt;/h1&amp;gt;
&amp;lt;h2&amp;gt;Your client does not have permission to get URL &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; from this server.&amp;lt;/h2&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update the sending service
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;sending-service/main.go&lt;/code&gt; file, add import the required packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
  ...
  "context"
  "time"
  "google.golang.org/api/idtoken"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a new function &lt;code&gt;httpClientWithIDToken&lt;/code&gt; to generate an authenticated HTTP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func httpClientWithIDToken(ctx context.Context, audience string) (*http.Client, error) {
  client, err := idtoken.NewClient(ctx, audience)
  if err != nil {
    return nil, err
  }
  return client, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;go mod tidy&lt;/code&gt; to download the required package.&lt;/p&gt;

&lt;p&gt;Modify the &lt;code&gt;main&lt;/code&gt; function to use the authenticated HTTP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func main() {
  receivingServiceURL := os.Getenv("RECEIVING_SERVICE_URL")
  if receivingServiceURL == "" {
    log.Fatal("RECEIVING_SERVICE_URL environment variable is not set")
  }

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
    defer cancel()

    client, err := httpClientWithIDToken(ctx, receivingServiceURL)
    if err != nil {
      log.Printf("Failed to create authenticated client: %v", err)
      http.Error(w, "Failed to create authenticated client", http.StatusInternalServerError)
      return
    }

    resp, err := client.Get(receivingServiceURL)
    if err != nil {
      log.Printf("Failed to make request: %v", err)
      http.Error(w, "Failed to make request", http.StatusInternalServerError)
      return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
      log.Printf("Failed to read response body: %v", err)
      http.Error(w, "Failed to read response body", http.StatusInternalServerError)
      return
    }

    fmt.Fprintf(w, "Response from receiving service: %s", string(body))
  })

  http.ListenAndServe(":8080", nil)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rebuild and redeploy the sending service:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud builds submit --tag gcr.io/${PROJECT_ID}/sending-service`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, deploy the sending service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud run deploy sending-service --image gcr.io/${PROJECT_ID}/sending-service --region ${REGION} --platform managed --allow-unauthenticated --set-env-vars RECEIVING_SERVICE_URL=${RECEIVING_SERVICE_URL}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test the authentication
&lt;/h2&gt;

&lt;p&gt;Visit the sending service URL in your browser, and you should now see a successful response from the receiving service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl ${SENDING_SERVICE_URL}                                                                                                                                                                               
Response from receiving service: Hello from the receiving service!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have identified our calling service to the receiving service, but we haven't set up the receiving service to accept requests from the calling service.&lt;/p&gt;

&lt;p&gt;By default, Cloud Run services utilize the Compute Engine default service account, which has the Project &amp;gt; Editor IAM role. This grants your Cloud Run revisions read and write access to all resources in your GCP project.&lt;/p&gt;

&lt;p&gt;To adhere to the principle of least privilege, we will create a new service account that has limited permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a new service account for the calling service
&lt;/h3&gt;

&lt;p&gt;Run the following command to create a new service account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud iam service-accounts create calling-service-sa --display-name "Calling Service Account"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Redeploy the calling service with the new service account
&lt;/h3&gt;

&lt;p&gt;Redeploy the calling service using the new service account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud run deploy sending-service --image gcr.io/${PROJECT_ID}/sending-service --region ${REGION} --platform managed --allow-unauthenticated --service-account calling-service-sa@${PROJECT_ID}.iam.gserviceaccount.com --set-env-vars RECEIVING_SERVICE_URL=${RECEIVING_SERVICE_URL}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you visit the sending service URL in your browser, you should see a failed response because the calling service doesn't have permission to invoke the receiving service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl ${SENDING_SERVICE_URL}                                                                   
Response from receiving service: 
&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv="content-type" content="text/html;charset=utf-8"&amp;gt;
&amp;lt;title&amp;gt;403 Forbidden&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body text=#000000 bgcolor=#ffffff&amp;gt;
&amp;lt;h1&amp;gt;Error: Forbidden&amp;lt;/h1&amp;gt;
&amp;lt;h2&amp;gt;Your client does not have permission to get URL &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; from this server.&amp;lt;/h2&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grant the calling service identity permission to invoke the receiving service
&lt;/h3&gt;

&lt;p&gt;To grant the calling service account permission to invoke the receiving service, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud run services add-iam-policy-binding receiving-service --region ${REGION} --member=serviceAccount:calling-service-sa@${PROJECT_ID}.iam.gserviceaccount.com --role=roles/run.invoker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Test the authentication
&lt;/h3&gt;

&lt;p&gt;Visit the sending service URL in your browser, and you should now see a successful response from the receiving service, authenticated using the new service account with limited permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl ${SENDING_SERVICE_URL}
Response from receiving service: Hello from the receiving service!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In this tutorial, we demonstrated how to set up service-to-service authentication for Google Cloud Run services using the Go programming language.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Customizing Django Authentication using AbstractBaseUser</title>
      <dc:creator>Joshua Masiko</dc:creator>
      <pubDate>Fri, 20 Mar 2020 08:34:35 +0000</pubDate>
      <link>https://dev.to/joshwizzy/customizing-django-authentication-using-abstractbaseuser-llg</link>
      <guid>https://dev.to/joshwizzy/customizing-django-authentication-using-abstractbaseuser-llg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Django has a builtin app that provides much of the required authentication machinery out of the box.&lt;br&gt;
It also allows you to customize authentication if the defaults don't match your requirements.&lt;br&gt;
There are multiple approaches you can take including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using a Proxy Model  based on &lt;code&gt;User&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adding a &lt;code&gt;OneToOneField&lt;/code&gt; that points to &lt;code&gt;User&lt;/code&gt; on a Profile model.&lt;/li&gt;
&lt;li&gt;Extending &lt;code&gt;AbstractUser&lt;/code&gt; and adding fields for additional profile information.&lt;/li&gt;
&lt;li&gt;Extending &lt;code&gt;AbstractBaseUser&lt;/code&gt; and implementing the required functionality.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can choose among the first 3 options if you wan't to add minimal customization to the Django defaults.&lt;br&gt;
If, however, you wan't to use an email address instead of a username as the user identifier, you have to extend &lt;code&gt;AbstractBaseUser&lt;/code&gt;.&lt;br&gt;
This article will explain how to do this using a bare-bones listings application as an example.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need to have working knowledge of Python and the Django Framework.&lt;br&gt;
The Django version used in this tutorial is 3.04.&lt;br&gt;
A recent version of Python 3. &lt;br&gt;
The instructions in this tutorial were performed on Ubuntu 18.04 LTS with Python 3.6.&lt;/p&gt;
&lt;h2&gt;
  
  
  High Level Description of the App
&lt;/h2&gt;

&lt;p&gt;Unauthenticated users can view all listings.&lt;br&gt;
Users have to register to post listings.&lt;br&gt;
The registration information includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mandatory fields: email, password, phone.&lt;/li&gt;
&lt;li&gt;Optional fields: date_of_birth, photo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Superusers can create staff users using the Django admin interface.&lt;br&gt;
Staff can flag listings using the Django admin interface.&lt;/p&gt;

&lt;p&gt;The most recent 30 unflagged listings are displayed on the home page.&lt;br&gt;
Users can click on a listing to view its details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;On Ubuntu 18.04 you might need to add the universe repository and install python3-venv.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo add-apt-repository universe
$ sudo apt install -y python3-venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Create and activate the 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;$ mkdir ~/.virtualenvs
$ python3 -m venv ~/.virtualenvs/listings
$ source ~/.virtualenvs/listings/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install Django and create a project using the &lt;code&gt;django-admin&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;(listings) $ pip install django==3.0.4
(listings) $ django-admin startproject listings
(listings) $ cd listings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now run the dev server and 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 browser.&lt;br&gt;
You should see the Django Welcome Page.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fa9a4079fec-welcome.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fa9a4079fec-welcome.PNG" alt="welcome.PNG" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Ignore the warning about unapplied migrations in the console. &lt;br&gt;
The recommended procedure is to apply migrations  &lt;strong&gt;after&lt;/strong&gt; setting up the custom user model.&lt;/p&gt;
&lt;h2&gt;
  
  
  The accounts app
&lt;/h2&gt;

&lt;p&gt;Let's add an app to host our authentiation customizations.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  The model and Manager
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# accounts/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.contrib.auth import get_user_model
from django.utils import timezone


class AccountManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, name, phone, password, **extra_fields):
        values = [email, name, phone]
        field_value_map = dict(zip(self.model.REQUIRED_FIELDS, values))
        for field_name, value in field_value_map.items():
            if not value:
                raise ValueError('The {} value must be set'.format(field_name))

        email = self.normalize_email(email)
        user = self.model(
            email=email,
            name=name,
            phone=phone,
            **extra_fields
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, name, phone, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, name, phone, password, **extra_fields)

    def create_superuser(self, email, name, phone, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, name, phone, password, **extra_fields)


class Account(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    name = models.CharField(max_length=150)
    phone = models.CharField(max_length=50)
    date_of_birth = models.DateField(blank=True, null=True)
    picture = models.ImageField(blank=True, null=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)
    last_login = models.DateTimeField(null=True)

    objects = AccountManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name', 'phone']

    def get_full_name(self):
        return self.name

    def get_short_name(self):
        return self.name.split()[0]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;USERNAME_FIELD&lt;/code&gt; is the name of the field on the user model that is used as the unique identifier.&lt;br&gt;
&lt;code&gt;REQUIRED_FIELDS&lt;/code&gt; are the mandatory fields other than the unique identifier&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;create_user&lt;/code&gt; and &lt;code&gt;create_superuser&lt;/code&gt; functions should accept the username field, plus all required fields as positional arguments.&lt;br&gt;
Django’s provides a &lt;code&gt;PermissionsMixin&lt;/code&gt; which we can include in the class hierarchy for our user model to support Django’s permissions.&lt;/p&gt;

&lt;p&gt;Add the accounts app to the list of installed apps in  &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 plaintext"&gt;&lt;code&gt;# listings/settings.py
...
INSTALLED_APPS = [
    ...
    'accounts.apps.AccountsConfig',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specify the custom model as the default user model using the &lt;code&gt;AUTH_USER_MODEL&lt;/code&gt; setting in &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 plaintext"&gt;&lt;code&gt;# listings/settings.py
...
AUTH_USER_MODEL = 'accounts.Account'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Pillow which is required by Django's ImageField&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(listings) $ pip install pillow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate migrations for the model now that the model is setup.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;And create 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;(listings) $ python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The forms
&lt;/h2&gt;

&lt;p&gt;Subclassing &lt;code&gt;AbstractBaseUser&lt;/code&gt; enables us to re-use the Django &lt;code&gt;auth&lt;/code&gt; app built-in forms and views.&lt;/p&gt;

&lt;p&gt;The following forms are compatible with any subclass of AbstractBaseUser:&lt;br&gt;
&lt;code&gt;AuthenticationForm&lt;/code&gt;: Uses the username field specified by &lt;code&gt;USERNAME_FIELD&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;SetPasswordForm&lt;/code&gt;: Allows users to change their password without entering the old password.&lt;br&gt;
&lt;code&gt;PasswordChangeForm&lt;/code&gt;: Allows users to change their password by entering the old password and a new password.&lt;br&gt;
&lt;code&gt;AdminPasswordChangeForm&lt;/code&gt;: Allows users to change their password from the Django admin.&lt;br&gt;
&lt;code&gt;PasswordResetForm&lt;/code&gt;: Assumes users to reset their passwords using a reset link sent to the email address.&lt;br&gt;
These forms are by the built-in  &lt;code&gt;auth&lt;/code&gt; views to which we delegate authentication and password management.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UserCreationForm&lt;/code&gt; and &lt;code&gt;UserChangeForm&lt;/code&gt;: Can be used to create users and change user details.&lt;br&gt;
These forms are tied to default Django &lt;code&gt;User&lt;/code&gt; model so we will provide our own implementations.&lt;br&gt;
We will also add a registration form for signing up users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# accounts/forms.py
from django import forms
from django.contrib.auth.models import Group
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from .models import Account


class RegistrationForm(forms.ModelForm):
    password = forms.CharField(label='Password', widget=forms.PasswordInput)
    class Meta:
        model = Account
        fields = ('email', 'name', 'phone', 'date_of_birth', 'picture', 'password')

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password"])
        if commit:
            user.save()
        return user


class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = Account
        fields = ('email', 'name', 'phone', 'date_of_birth', 'picture', 'is_staff', 'is_superuser')

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user


class UserChangeForm(forms.ModelForm):
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = Account
        fields = ('email', 'name', 'phone', 'date_of_birth', 'picture', 'password', 'is_active', 'is_superuser')

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The views
&lt;/h2&gt;

&lt;p&gt;We implement views for user registration and profile editing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.shortcuts import render

from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login as auth_login, logout
from django.contrib import messages
from django.contrib.auth.forms import AuthenticationForm
from django.shortcuts import render

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse

from .models import Account
from .forms import RegistrationForm


class RegistrationView(CreateView):
    template_name = 'registration/register.html'
    form_class = RegistrationForm

    def get_context_data(self, *args, **kwargs):
        context = super(RegistrationView, self).get_context_data(*args, **kwargs)
        context['next'] = self.request.GET.get('next')
        return context

    def get_success_url(self):
        next_url = self.request.POST.get('next')
        success_url = reverse('login')
        if next_url:
            success_url += '?next={}'.format(next_url)

        return success_url


class ProfileView(UpdateView):
    model = Account
    fields = ['name', 'phone', 'date_of_birth', 'picture']
    template_name = 'registration/profile.html'

    def get_success_url(self):
        return reverse('index')

    def get_object(self):
        return self.request.user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The admin
&lt;/h2&gt;

&lt;p&gt;Now we register the custom user model with Django’s admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from .models import Account
from .forms import UserCreationForm, UserChangeForm


class AccountAdmin(BaseUserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm

    list_display = ('email', 'name', 'phone', 'date_of_birth', 'is_staff',  'is_superuser')
    list_filter = ('is_superuser',)

    fieldsets = (
        (None, {'fields': ('email', 'is_staff', 'is_superuser', 'password')}),
        ('Personal info', {'fields': ('name', 'phone', 'date_of_birth', 'picture')}),
        ('Groups', {'fields': ('groups',)}),
        ('Permissions', {'fields': ('user_permissions',)}),
    )
    add_fieldsets = (
        (None, {'fields': ('email', 'is_staff', 'is_superuser', 'password1', 'password2')}),
        ('Personal info', {'fields': ('name', 'phone', 'date_of_birth', 'picture')}),
        ('Groups', {'fields': ('groups',)}),
        ('Permissions', {'fields': ('user_permissions',)}),
    )

    search_fields = ('email', 'name', 'phone')
    ordering = ('email',)
    filter_horizontal = ()


admin.site.register(Account, AccountAdmin)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the app so far
&lt;/h2&gt;

&lt;p&gt;We should now able to create users and edit their details.&lt;/p&gt;

&lt;p&gt;Let's create a superuser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(listings) $ python manage.py createsuperuser
Email: admin@example.com
Name: Admin User
Phone: 123456
Password:
Password (again):
Superuser created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the Django dev server then navigate to &lt;a href="http://127.0.0.1:8000/admin" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin&lt;/a&gt; in your browser and login with the credentials you specified.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F046a8dd632-admin.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F046a8dd632-admin.PNG" alt="admin.PNG" width="800" height="400"&gt;&lt;/a&gt; &lt;br&gt;
You should be able to add and edit users.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core app
&lt;/h2&gt;

&lt;p&gt;Now let's implement the core app functionality which includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anyone can view listings.&lt;/li&gt;
&lt;li&gt;Users can register to create accounts.&lt;/li&gt;
&lt;li&gt;Registered users can post listings&lt;/li&gt;
&lt;li&gt;Logged in users can change their passwords.&lt;/li&gt;
&lt;li&gt;Logged in users can edit their profiles.&lt;/li&gt;
&lt;li&gt;Staff users can flag listings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use Bootstrap 4 and django-crispy-forms for styling the application interface and forms.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install django-crispy-forms
&lt;/h2&gt;

&lt;p&gt;Install django-crispy-form using pip&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(listings) $ pip install django-crispy-forms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add crispy_forms the the installed apps list in your settings file and set the default styling to bootstrap4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# listings/settings.py
...
INSTALLED_APPS = [
    ...
    'crispy_forms',
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the app&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then add it to the installed apps list in settings.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# listings/settings.py
INSTALLED_APPS = [
...
    'core.apps.CoreConfig',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the user model has a &lt;code&gt;picture&lt;/code&gt; field let's set up &lt;code&gt;MEDIA_URL&lt;/code&gt; and &lt;code&gt;MEDIA_ROOT&lt;/code&gt; to tell Django where to save uploaded images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# listings/settings.py
...
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;auth&lt;/code&gt; app password reset view sends an email to the user containing instructions and a URL. &lt;br&gt;
We will use the console email backend for testing purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# listings/settings.py
...
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also want to redirect to the home view on login and logout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The models
&lt;/h3&gt;

&lt;p&gt;Each listing is associated with the user and category.&lt;br&gt;
We also track the date and time that the listing was posted.&lt;br&gt;
Listings may be flagged. We record the timestamp and user who flagged the listing.&lt;br&gt;
By default listings will be sorted in reverse chronological order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/models.py
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.urls import reverse
from django.template.defaultfilters import slugify


class Category(models.Model):
    name = models.CharField(max_length=150)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "Categories"


class Listing(models.Model):
    title = models.CharField(max_length=150)
    content = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.PROTECT)
    expiry_date = models.DateField(null=True, blank=True)
    location = models.CharField(max_length=150, null=True, blank=True)
    slug = models.SlugField()

    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name='listings'
    )
    created_at = models.DateTimeField(default=timezone.now)

    flagged = models.BooleanField(default=False)
    flagged_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        null=True,
        blank=True
    )
    flagged_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ['-created_at']

    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = slugify(self.title)

        return super(Listing, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('listing',
                       args=[self.slug])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now create migrations for the app and sync the database with the models.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  The admin
&lt;/h2&gt;

&lt;p&gt;We register the &lt;code&gt;Category&lt;/code&gt; and &lt;code&gt;Listing&lt;/code&gt; models with the Django admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/admin.py
from django.contrib import admin
from django.utils import timezone

from .models import Category, Listing


class ListingAdmin(admin.ModelAdmin):
    list_display = ('category', 'title', 'content', 'created_by', 'flagged', 'flagged_by', 'flagged_at')
    list_filter = ('flagged',)
    exclude = ['slug', 'created_by', 'created_at', 'flagged_at', 'flagged_by']

    def save_model(self, request, obj, form, change):
        if not obj.pk:
            obj.created_by = request.user
        if obj.flagged:
            obj.flagged_by = request.user
            obj.flagged_at = timezone.now()
        else:
            obj.flagged_by = obj.flagged_at = None

        super().save_model(request, obj, form, change)


admin.site.register(Category)
admin.site.register(Listing, ListingAdmin)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to &lt;a href="http://127.0.0.1:8000/admin" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin&lt;/a&gt;.&lt;br&gt;
Login as the superuser and you will see new entries for Categories and Listings displayed.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fdad4eef975-coreadmin.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fdad4eef975-coreadmin.PNG" alt="coreadmin.PNG" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Clicking the &lt;code&gt;Add&lt;/code&gt; button and create some test categories.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fbe176fafe8-Categories.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fbe176fafe8-Categories.PNG" alt="Categories.PNG" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  The views
&lt;/h2&gt;

&lt;p&gt;The application functionality consists of 3 views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The default view displays the most recent 30 listings.&lt;/li&gt;
&lt;li&gt;A view to post a listing, restricted to logged-in users.&lt;/li&gt;
&lt;li&gt;A view to display a listings details.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/views.py
from django.shortcuts import render

from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404
from django.views.generic import DetailView
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView
from django.urls import reverse

from .models import Listing


class HomeView(ListView):
    template_name = 'home.html'
    queryset = Listing.objects.filter(flagged=False)
    context_object_name = 'listings'
    paginate_by = 30


class CreateListing(LoginRequiredMixin, CreateView):
    model = Listing
    fields = ['title', 'content', 'category', 'expiry_date', 'location']
    template_name = 'add_listing.html'

    def get_success_url(self):
        return reverse('home')

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)


class ListingView(DetailView):
    template_name = 'listing.html'
    model = Listing

    def get_object(self):
        obj = super(ListingView, self).get_object()
        if obj.flagged:
            raise Http404()
        return obj

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;Let's start with th project-level URLs. We will re-use the &lt;code&gt;auth&lt;/code&gt; app views for Login, Logout, Password Change &amp;amp; Password Reset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# listings/urls.py
from django.contrib import admin
from django.urls import path, include
from django.contrib.auth import views as auth_views

from accounts import views
from accounts.views import RegistrationView, ProfileView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('core.urls')),

    path('register/', RegistrationView.as_view(), name='register'),
    path('profile/', ProfileView.as_view(), name='profile'),
    path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
    path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),

    path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),

    path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/&amp;lt;uidb64&amp;gt;/&amp;lt;token&amp;gt;/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the core app URLs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/urls.py
from django.urls import path, include

from .views import HomeView, CreateListing, ListingView

urlpatterns = [
    path('', HomeView.as_view(), name='home'),
    path('add-listing', CreateListing.as_view(), name='add_listing'),
    path('listings/&amp;lt;slug:slug&amp;gt;/', ListingView.as_view(), name='listing'),
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;auth&lt;/code&gt; views render templates from a &lt;code&gt;registration&lt;/code&gt; folder. &lt;br&gt;
We will create a project level &lt;code&gt;templates&lt;/code&gt; folder for all our templates and a &lt;code&gt;registration&lt;/code&gt; subfolder.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then add the &lt;code&gt;templates&lt;/code&gt; folder to the &lt;code&gt;DIRS&lt;/code&gt; setting in &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 plaintext"&gt;&lt;code&gt;TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The templates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;auth&lt;/code&gt; templates
&lt;/h3&gt;

&lt;p&gt;These templates will be used by the &lt;code&gt;auth&lt;/code&gt; app views.&lt;br&gt;
Auth templates will exend a base template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!--templates/registration/auth_base.html--&amp;gt;
&amp;lt;!doctype  html&amp;gt;
&amp;lt;html  lang="en"&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"&amp;gt;
&amp;lt;title&amp;gt;Listings&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;div  class="container"&amp;gt;
    &amp;lt;div  class="row justify-content-center"&amp;gt;
      &amp;lt;div  class="col-4"&amp;gt;
      &amp;lt;h1  class="text-center"&amp;gt;Listings&amp;lt;/h1&amp;gt;
      {% block content %}

      {% endblock %}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login template used by &lt;code&gt;LoginView&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;&amp;lt;!--templates/registration/login.html--&amp;gt;
{% extends "registration/auth_base.html" %}
{% load crispy_forms_tags %}

{% block content %}

&amp;lt;div class="card"&amp;gt;
  &amp;lt;div class="card-body"&amp;gt;
    &amp;lt;h4 class="card-title"&amp;gt;Log in to your account&amp;lt;/h4&amp;gt;
    &amp;lt;form method="post"&amp;gt;
      {% csrf_token %}
      &amp;lt;input type="hidden" name="next" value="{{ next }}"&amp;gt;{{ form|crispy }}&amp;lt;button type="submit"
        class="btn btn-primary btn-block"&amp;gt;Log in&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="card-footer"&amp;gt;
    Forgot your password? &amp;lt;a href="{% url "password_reset" %}"&amp;gt;click here&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
    New user? &amp;lt;a href="{% url "register" %}?next={{ next }}"&amp;gt;create new account&amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register template used by &lt;code&gt;RegistrationView&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;&amp;lt;!--templates/registration/register.html--&amp;gt;
{% extends "registration/auth_base.html" %}
{% load crispy_forms_tags %}

{% block content %}

&amp;lt;div class="card"&amp;gt;
    &amp;lt;div class="card-body"&amp;gt;
        &amp;lt;h4 class="card-title"&amp;gt;Signup for an account&amp;lt;/h4&amp;gt;
        &amp;lt;form method="post" enctype="multipart/form-data"&amp;gt;
            {% csrf_token %}
            {{form|crispy}}
            &amp;lt;input type="hidden" name="next" value="{{ next }}"&amp;gt;
            &amp;lt;button type="submit" class="btn btn-primary btn-block"&amp;gt;Register&amp;lt;/button&amp;gt;

        &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change password template used by &lt;code&gt;PasswordChangeView&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;&amp;lt;!--templates/registration/password_change_form.html--&amp;gt;
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}

&amp;lt;div class="row justify-content-center"&amp;gt;
    &amp;lt;div class="col-4"&amp;gt;
        &amp;lt;div class="card"&amp;gt;
            &amp;lt;div class="card-body"&amp;gt;
                &amp;lt;h4 class="card-title"&amp;gt;Change your password&amp;lt;/h4&amp;gt;
                &amp;lt;form method="POST" enctype="multipart/form-data"&amp;gt;
                    {% csrf_token %}
                    {{form|crispy}}
                    &amp;lt;button type="submit" class="btn btn-primary btn-block"&amp;gt;Change Password&amp;lt;/button&amp;gt;

                &amp;lt;/form&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password change done template used by &lt;code&gt;PasswordChangeDoneView&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;&amp;lt;!--templates/registration/password_change_done.html--&amp;gt;
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}

&amp;lt;div class="row justify-content-center"&amp;gt;
    &amp;lt;div class="col-8"&amp;gt;
        &amp;lt;div class="card"&amp;gt;
            &amp;lt;div class="card-body"&amp;gt;
                &amp;lt;h2&amp;gt;Your password has changed&amp;lt;/h2&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password Reset template used by &lt;code&gt;PasswordResetView&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;&amp;lt;!--templates/registration/password_reset_form.html--&amp;gt;
{% extends "registration/auth_base.html" %}
{% load crispy_forms_tags %}

{% block content %}

&amp;lt;div class="card"&amp;gt;
    &amp;lt;div class="card-body"&amp;gt;
        &amp;lt;h4 class="card-title"&amp;gt;Reset your password&amp;lt;/h4&amp;gt;
        &amp;lt;form method="post"&amp;gt;
            {% csrf_token %}
            {{form|crispy}}
            &amp;lt;button type="submit" class="btn btn-primary btn-block"&amp;gt;Reset&amp;lt;/button&amp;gt;

        &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password reset email template used by &lt;code&gt;PasswordResetView&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="c"&gt;&amp;lt;!--templates/registration/password_reset_email.html--&amp;gt;&lt;/span&gt;
{% autoescape off %}
You're receiving this email because you requested a password reset for your user account at {{ site_name }}.

Please go to the following page and choose a new password:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
Your username, in case you’ve forgotten: {{ user.get_username }}

Thanks for using our site!

The {{ site_name }} team

{% endautoescape %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password reset done template used by &lt;code&gt;PasswordResetDoneView&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;&amp;lt;!--registration/templates/password_reset_done.html--&amp;gt;
{% extends "registration/auth_base.html" %}

{% block content %}

&amp;lt;div class="card"&amp;gt;
    &amp;lt;div class="card-body"&amp;gt;


        &amp;lt;p&amp;gt;We’ve emailed you instructions for setting your password, if an account exists with the email you entered.
            You should receive them shortly.&amp;lt;/p&amp;gt;

        &amp;lt;p&amp;gt;If you don’t receive an email, please make sure you’ve entered the address you registered with, and check
            your spam folder.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password reset confirm template used by &lt;code&gt;PasswordResetConfirmView&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;&amp;lt;!--templates/registration/password_reset_confirm.html--&amp;gt;
{% extends "registration/auth_base.html" %}
{% load crispy_forms_tags %}

{% block content %}

&amp;lt;div class="card"&amp;gt;
    &amp;lt;div class="card-body"&amp;gt;

        {% if validlink %}

        &amp;lt;p&amp;gt;Please enter your new password twice so we can verify you typed it in correctly.&amp;lt;/p&amp;gt;

        &amp;lt;form method="post"&amp;gt;{% csrf_token %}
            {{form|crispy}}
            &amp;lt;button type="submit" class="btn btn-primary btn-block"&amp;gt;Change my password&amp;lt;/button&amp;gt;

        &amp;lt;/form&amp;gt;

        {% else %}

        &amp;lt;p&amp;gt;The password reset link was invalid, possibly because it has already been used. Please request a new password
            reset.&amp;lt;/p&amp;gt;

        {% endif %}
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password reset complete template used by &lt;code&gt;PasswordResetCompleteView&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;&amp;lt;!--templates/registration/password_reset_complete.html--&amp;gt;
{% extends "registration/auth_base.html" %}
{% block content %}

&amp;lt;div class="card"&amp;gt;
    &amp;lt;div class="card-body"&amp;gt;

        &amp;lt;p&amp;gt;Your password has been set. You may go ahead and log in now.&amp;lt;/p&amp;gt;

        &amp;lt;p&amp;gt;&amp;lt;a href="{{ login_url }}"&amp;gt;Log in&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit Profile template used by &lt;code&gt;ProfileView&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;&amp;lt;!--templates/registration/profile.html--&amp;gt;
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}

&amp;lt;div class="row justify-content-center"&amp;gt;
    &amp;lt;div class="col-4"&amp;gt;
        &amp;lt;div class="card"&amp;gt;
            &amp;lt;div class="card-body"&amp;gt;
                &amp;lt;h4 class="card-title"&amp;gt;Update your profile&amp;lt;/h4&amp;gt;
                &amp;lt;form method="POST" enctype="multipart/form-data"&amp;gt;
                    {% csrf_token %}
                    {{form|crispy}}
                    &amp;lt;button type="submit" class="btn btn-primary btn-block"&amp;gt;Update Profile&amp;lt;/button&amp;gt;

                &amp;lt;/form&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;core&lt;/code&gt; templates
&lt;/h3&gt;

&lt;p&gt;These templates will be used by the &lt;code&gt;core&lt;/code&gt; app and should be saved in the project level &lt;code&gt;templates&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;The Base Template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!--templates/base.html--&amp;gt;
&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang="en"&amp;gt;

&amp;lt;head&amp;gt;
    &amp;lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"&amp;gt;
    &amp;lt;link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"&amp;gt;

    &amp;lt;title&amp;gt;Listings&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body {
            padding-top: 5rem;
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;

    &amp;lt;nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"&amp;gt;
        &amp;lt;a href="{% url "home" %}" class="navbar-brand"&amp;gt;Listings&amp;lt;/a&amp;gt;
        &amp;lt;button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse"&amp;gt;
            &amp;lt;span class="navbar-toggler-icon"&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;/button&amp;gt;
        {% if user.is_authenticated %}
        &amp;lt;div id="navbarCollapse" class="collapse navbar-collapse"&amp;gt;

            &amp;lt;ul class="nav navbar-nav ml-auto"&amp;gt;
                &amp;lt;li class="nav-item dropdown"&amp;gt;
                    &amp;lt;a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown"&amp;gt;{{user.name}}&amp;lt;/a&amp;gt;
                    &amp;lt;div class="dropdown-menu dropdown-menu-right"&amp;gt;
                        &amp;lt;a href="{% url "profile" %}" class="dropdown-item"&amp;gt;Profile&amp;lt;/a&amp;gt;
                        &amp;lt;a href="{% url "password_change" %}" class="dropdown-item"&amp;gt;Change Password&amp;lt;/a&amp;gt;
                        &amp;lt;div class="dropdown-divider"&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;a href="{% url "logout" %}" class="dropdown-item"&amp;gt;Logout&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
        {% endif %}
    &amp;lt;/nav&amp;gt;

    &amp;lt;main role="main" class="container"&amp;gt;
        {% block content %}

        {% endblock %}

    &amp;lt;/main&amp;gt;
    &amp;lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Home template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!--templates/home.html--&amp;gt;
{% extends 'base.html' %}
{% block content %}

&amp;lt;a href="{% url "add_listing" %}"&amp;gt;Add Listing&amp;lt;/a&amp;gt;
&amp;lt;h3 class="mt-2"&amp;gt;Latest Listings&amp;lt;/h3&amp;gt;
&amp;lt;div class="col-md-8 mt-3"&amp;gt;    
    {% for listing in listings %}
    &amp;lt;div class="card"&amp;gt;   
      &amp;lt;div class="card-body"&amp;gt;


      &amp;lt;blockquote class="blockquote mb-0"&amp;gt;
        &amp;lt;p&amp;gt;{{listing.title}}&amp;lt;small&amp;gt;&amp;lt;a href="{{listing.get_absolute_url}}" &amp;gt;&amp;amp;nbsp;View Details &amp;amp;rarr;&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;footer class="blockquote-footer"&amp;gt;&amp;lt;small&amp;gt;Posted on {{listing.created_at|date:"M d, Y"}} by
            &amp;lt;a href="#"&amp;gt;{{listing.created_by.name}}&amp;lt;/a&amp;gt; in &amp;lt;a href=""&amp;gt;{{listing.category}}&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;&amp;lt;/footer&amp;gt;
      &amp;lt;/blockquote&amp;gt;
    &amp;lt;/div&amp;gt;
    {% endfor %}
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listing detail template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!--templates/listing.html--&amp;gt;
{% extends 'base.html' %}
{% block content %}

&amp;lt;div class="card mb-3"&amp;gt;
    &amp;lt;div class="card-body"&amp;gt;
      &amp;lt;h5 class="card-title"&amp;gt;{{listing.title}}&amp;lt;/h5&amp;gt;
      &amp;lt;p class="card-text"&amp;gt;{{listing.content}}&amp;lt;/p&amp;gt;
      &amp;lt;p class="card-text"&amp;gt;&amp;lt;small class="text-muted"&amp;gt;Posted by {{listing.created_by.name}} on {{listing.created_at}}&amp;lt;/small&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Listing template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!--templates/add_listing.html--&amp;gt;
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
&amp;lt;div class="row"&amp;gt;
&amp;lt;form method="POST" enctype="multipart/form-data"&amp;gt;
    {% csrf_token %}
    {{form |crispy}}
    &amp;lt;button type="submit" class="btn btn-primary btn-block"&amp;gt;Add Listing&amp;lt;/button&amp;gt;

&amp;lt;/form&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Testing the app
&lt;/h1&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 browser.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F2427f6598f-home.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F2427f6598f-home.PNG" alt="home.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking &lt;code&gt;Add Listing&lt;/code&gt; should take you to the login form.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fef96876ba6-login.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fef96876ba6-login.PNG" alt="login.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;create new account&lt;/code&gt; and signup for an account&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fa8a5595f3b-signup.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2Fa8a5595f3b-signup.PNG" alt="signup.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon creation of the account you will be redirected to the login form.&lt;br&gt;
Login using the credentials you specified and you will be taken to the Listing creation form.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F9b62595329-add_listing.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F9b62595329-add_listing.PNG" alt="add_listing.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in some details and creating a listing and the listing. You will be redirected to the home page.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F18d768f043-listings.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F18d768f043-listings.PNG" alt="listings.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can test password change and profile edit by clicking using the dropdown menu in the upper right corner&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F2147aebc4d-menu.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F2147aebc4d-menu.PNG" alt="menu.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test password reset, logout then click the forgot password link. &lt;br&gt;
Enter your email and check the console for the email containing the reset link.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F9f3b8e305b-reset.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F9f3b8e305b-reset.PNG" alt="reset.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F19e6d23d35-email.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F19e6d23d35-email.PNG" alt="email.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the reset URL in your browser and you will be prompted to enter your new password.&lt;br&gt;
Enter your new password and a confirmation is displayed upon successful change.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F710901c798-confirm_reset.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F710901c798-confirm_reset.PNG" alt="confirm_reset.PNG" width="800" height="400"&gt;&lt;/a&gt; &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F14a6417a40-resetdone.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F19%2F14a6417a40-resetdone.PNG" alt="resetdone.PNG" width="800" height="400"&gt;&lt;/a&gt; &lt;br&gt;
You can then click the &lt;code&gt;Log in&lt;/code&gt; link and enter your new password to login.&lt;/p&gt;

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

&lt;p&gt;This article has shown how to customize Django authentication to implement the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using an email address instead of a username as the identification token.&lt;/li&gt;
&lt;li&gt;Django admin integration for the custom user model.&lt;/li&gt;
&lt;li&gt;User signup, login.&lt;/li&gt;
&lt;li&gt;Password change and reset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/joshwizzy/listings" rel="noopener noreferrer"&gt;The Source code for this article can be found here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
    </item>
    <item>
      <title>How to secure a Web Application running on a private network.</title>
      <dc:creator>Joshua Masiko</dc:creator>
      <pubDate>Fri, 13 Mar 2020 18:11:23 +0000</pubDate>
      <link>https://dev.to/joshwizzy/how-to-secure-a-web-application-running-on-a-private-network-el2</link>
      <guid>https://dev.to/joshwizzy/how-to-secure-a-web-application-running-on-a-private-network-el2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article describes how to secure a web app running on a private network under these constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server is not reachable via the public internet.&lt;/li&gt;
&lt;li&gt;The server is only reachable by clients on a LAN using private IP addresses.&lt;/li&gt;
&lt;li&gt;The internet gateway for the server &lt;em&gt;may&lt;/em&gt; not have a  Static Public IP.&lt;/li&gt;
&lt;li&gt;We do not want to use a self-signed certificate.&lt;/li&gt;
&lt;li&gt;The certificate generation process should be automated.&lt;/li&gt;
&lt;li&gt;The certificate renewal process should also be automated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ubuntu 18.04 LTS running on a server reachable over the local network.&lt;br&gt;
The server should be able to connect to the internet to download the required software.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A registered domain with any of the popular registrars with a panel where you can update the Name Server entries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article: &lt;br&gt;
The network segment is &lt;strong&gt;192.168.1.0/24&lt;/strong&gt; and the server IP address is &lt;strong&gt;192.168.1.4&lt;/strong&gt;&lt;br&gt;
We will use the domain  &lt;strong&gt;private.hamsterdam.me&lt;/strong&gt; as an example.&lt;/p&gt;
&lt;h2&gt;
  
  
  The components in the stack are:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ubuntu Linux 18.04 LTS&lt;/strong&gt; will run the Webserver, Firewall and certificate generation and renewal tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NGINX web server&lt;/strong&gt; will terminate HTTPS traffic. It can proxy traffic to an application running on the same server or another machine on the LAN&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's Encrypt Certbot&lt;/strong&gt; will automate TLS/SSL certificate generation and renewal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Digital Ocean&lt;/strong&gt; will handle management of the DNS zone. Certbot has a plugin for the Digital Ocean API that uses the dns-01 challenge which involves creating TXT records. The dns-01 challenge allows us to get around the http-01 challenge requirement to have a public IP reachable via the internet.&lt;/p&gt;

&lt;p&gt;These steps were performed on a fresh installation of Ubuntu 18.04 server.&lt;/p&gt;
&lt;h2&gt;
  
  
  First, we install NGINX
&lt;/h2&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo add-apt-repository universe
sudo apt update
sudo apt install -y nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We will use the ufw firewall to manage access to the server. The firewall is disabled by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw status

Status: inactive
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's list ufw default application profiles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw app list
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will allow HTTP and HTTPS and SSH traffic through the firewall.&lt;br&gt;
HTTP maps to port 80 (normal, unencrypted web traffic) and HTTPS to port 443 (TLS/SSL encrypted traffic).&lt;/p&gt;

&lt;p&gt;We will also allow OpenSSH since we need to login to the server over the network.&lt;br&gt;
The &lt;em&gt;Nginx Full&lt;/em&gt; profile allows both HTTP and HTTPS traffic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now enable the firewall&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw enable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Check the status again and confirm the firewall is active and SSH and HTTP(S) are allowed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can confirm that that web server is reachable by navigating to &lt;a href="http://192.168.1.4"&gt;http://192.168.1.4&lt;/a&gt; from another machine on the local network.&lt;br&gt;
We should see the NGINX welcome page.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Rzryv3a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/665fbab876-nginx-welcome.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Rzryv3a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/665fbab876-nginx-welcome.PNG" alt="nginx-welcome.PNG"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The next step is to set up the NGINX web root and server block for our domain.&lt;br&gt;
First create the web root folder and apply the necessary permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir -p /var/www/private.hamsterdam.me/html
sudo chown -R $USER:$USER /var/www/private.hamsterdam.me/html/
sudo chmod -R 755 /var/www/private.hamsterdam.me/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add an index file using your favorite editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vi /var/www/private.hamsterdam.me/html/index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Paste this content into the file and save.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Welcome to private.hamsterdam.me&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Set up server block.&lt;br&gt;
The ubuntu NGINX convention is to add configuration files to the &lt;em&gt;sites-available&lt;/em&gt; folder&lt;br&gt;
and then enable them by creating symbolic links in the &lt;em&gt;sites-enabled&lt;/em&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo vi /etc/nginx/sites-available/private.hamsterdam.me
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add the following content to the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
        listen 80;
        listen [::]:80;

        root /var/www/private.hamsterdam.me/html;
        index index.html index.htm index.nginx-debian.html;

        server_name private.hamsterdam.me;

        location / {
                try_files $uri $uri/ =404;
        }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now create a symbolic link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/private.hamsterdam.me /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Verify that the NGINX settings are ok and reload the configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nginx -t
sudo nginx -s reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;p&gt;This step requires you to update the DNS NS records at your registrar to point to the Digital Ocean servers.&lt;/p&gt;

&lt;p&gt;Digital Ocean provides detailed instructions at  &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-point-to-digitalocean-nameservers-from-common-domain-registrars"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To confirm the DNS settings for our domain lets install whois.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install -y whois

whois hamsterdam.me
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The response will have multiple line lines. We are interested in lines that start with &lt;em&gt;Name Server&lt;/em&gt; to confirm that NS records are set up correctly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name Server: NS1.DIGITALOCEAN.COM
Name Server: NS2.DIGITALOCEAN.COM
Name Server: NS3.DIGITALOCEAN.COM
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the Digital Ocean control panel for your domain add an A record for the domain that will serve the web app.&lt;br&gt;
Normally we would use the public IP of the server but here we use the private IP of the server.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6SxcfOAK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/707beebf4f-dns.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6SxcfOAK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/707beebf4f-dns.PNG" alt="dns.PNG"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;After adding the record ping private.hamsterdam.me from a machine on the Local network until it resolves successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ping private.hamsterdam.me

Pinging private.hamsterdam.me [192.168.1.4] with 32 bytes of data:
Reply from 192.168.1.4: bytes=32 time&amp;lt;1ms TTL=64
Reply from 192.168.1.4: bytes=32 time&amp;lt;1ms TTL=64
Reply from 192.168.1.4: bytes=32 time&amp;lt;1ms TTL=64
Reply from 192.168.1.4: bytes=32 time&amp;lt;1ms TTL=64

Ping statistics for 192.168.1.4:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now confirm that the NGINX block is correctly set up  by navigatiing to &lt;a href="http://private.hamsterdam.me"&gt;http://private.hamsterdam.me&lt;/a&gt;&lt;br&gt;
You should see the welcome page we added earlier.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NnqQONhb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/a353ffd7d7-private_dns.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NnqQONhb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/a353ffd7d7-private_dns.PNG" alt="private_dns.PNG"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  Let's Encrypt Certbot
&lt;/h2&gt;

&lt;p&gt;We will now install the certificate for private.hamsterdam.me using the certbot client.&lt;/p&gt;

&lt;p&gt;Add the Certbot repository for Ubuntu.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo add-apt-repository ppa:certbot/certbot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Press ENTER at the prompt.&lt;/p&gt;

&lt;p&gt;Then install Certbot’s Nginx package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install -y python-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will use the Digital Ocean certbot plugin to automate certificates generation and renewal.&lt;/p&gt;

&lt;p&gt;Install the plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install -y python3-certbot-dns-digitalocean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The plugin authenticates to the Digital Ocean API using a token obtained from the &lt;a href="https://cloud.digitalocean.com/settings/api/tokens"&gt;Digital Ocean account’s Applications &amp;amp; API Tokens page.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tgcmjylk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/69c1e963f4-tokens.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tgcmjylk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/69c1e963f4-tokens.PNG" alt="tokens.PNG"&gt;&lt;/a&gt; &lt;br&gt;
Click &lt;em&gt;Generate New Token&lt;/em&gt;.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hrgRTWfs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/6f1388851a-newtoken.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hrgRTWfs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/6f1388851a-newtoken.PNG" alt="newtoken.PNG"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Then enter a name for the token and click  &lt;em&gt;Generate Token&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The token is displayed in the list as a long hexadecimal value.&lt;br&gt;
&lt;em&gt;Make sure you copy this value as it is only displayed once&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, create a folder for the credentials file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p .secrets/certbot/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Use your favorite editor to create the credentials file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vi .secrets/certbot/digitalocean.ini

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then add an entry to the credentials file that looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The value after the equals sign should be the token you copied from the Digital Ocean panel.&lt;/p&gt;

&lt;p&gt;Update the file permissions to prevent the file from being read by other users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 600 ~/.secrets/certbot/digitalocean.ini
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Generate the certificate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot --authenticator dns-digitalocean --installer nginx --dns-digitalocean-credentials ~/.secrets/certbot/digitalocean.ini
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When prompted, Enter you email address, Agree to the terms of service and Choose whether to share your email or not.&lt;/p&gt;

&lt;p&gt;You will then be prompted to select the domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: private.hamsterdam.me
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this case there is only one entry so select 1 and press ENTER.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We choose option 2 so that unencrypted requests are redirected to the secure port.&lt;/p&gt;

&lt;p&gt;If all goes well you will see a congratulatory message and further NOTES.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Congratulations! You have successfully enabled https://private.hamsterdam.me
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we inspect our NGINX conf we will see that certbot has made various changes to&lt;br&gt;
enable HTTPS and redirect HTTP traffic to the secure port.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;less /etc/nginx/sites-enabled/private.hamsterdam.me

server {

        root /var/www/private.hamsterdam.me/html;
        index index.html index.htm index.nginx-debian.html;

        server_name private.hamsterdam.me;

        location / {
                try_files $uri $uri/ =404;
        }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/private.hamsterdam.me/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/private.hamsterdam.me/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = private.hamsterdam.me) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80;
        listen [::]:80;

        server_name private.hamsterdam.me;
    return 404; # managed by Certbot


}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now reload the NGINX conf.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nginx -s reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now for the moment of truth.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="http://private.hamsterdam.me"&gt;http://private.hamsterdam.me&lt;/a&gt;&lt;br&gt;
The browser will be redirected to &lt;a href="https://private.hamsterdam.me"&gt;https://private.hamsterdam.me&lt;/a&gt; and you should see the lock icon displayed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V6h1ZnwC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/8d5aca4188-https_redirect.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V6h1ZnwC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l2earn.nyc3.digitaloceanspaces.com/l2earn/images/uploads/2020/02/12/8d5aca4188-https_redirect.PNG" alt="https_redirect.PNG"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Certbot also adds a cron entry for renewing the certificate which you can inspect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;less /etc/cron.d/certbot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>nginx</category>
      <category>webdev</category>
      <category>certbot</category>
      <category>digitalocean</category>
    </item>
    <item>
      <title>How to Keep Secrets out of Django Settings</title>
      <dc:creator>Joshua Masiko</dc:creator>
      <pubDate>Fri, 13 Mar 2020 18:02:45 +0000</pubDate>
      <link>https://dev.to/joshwizzy/how-to-keep-secrets-out-of-django-settings-41c4</link>
      <guid>https://dev.to/joshwizzy/how-to-keep-secrets-out-of-django-settings-41c4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The Django startproject command creates a settings file that includes values that should be kept secret. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;SECRET_KEY&lt;/em&gt; is a value used when generating sessions and password reset tokens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;DATABASE_SETTINGS&lt;/em&gt; specifies the database connection settings and may include database credentials.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The DEBUG setting is also of interest:&lt;br&gt;
&lt;em&gt;DEBUG&lt;/em&gt; is a boolean value which determines whether Django displays detailed tracebacks are displayed when exceptions are raised.&lt;/p&gt;

&lt;p&gt;The deployment checklist recommends that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;DEBUG&lt;/em&gt; be set to &lt;em&gt;False&lt;/em&gt; to avoid leaking sensitive information from the detailed tracebacks that Django displays&lt;/li&gt;
&lt;li&gt;Secrets and database credentials be kept out of source control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also consistent with the Twelve-factor recommendation to keep Config separate from Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.12factor.net/config"&gt;Twelve-factor&lt;/a&gt;  is a useful set of recommendations for building web applications.&lt;/p&gt;
&lt;h2&gt;
  
  
  Django-environ
&lt;/h2&gt;

&lt;p&gt;Django-environ is a third-party Django library that can be used to manage environment variables.&lt;br&gt;
Values are read on startup instead of hardcoded in the settings file. The benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your don't have to use multiple settings files to manage different deployments (e.g staging, production, developer).&lt;/li&gt;
&lt;li&gt;Secrets can be kept out of source control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It reads values from a &lt;em&gt;.env&lt;/em&gt; file in the same folder as &lt;em&gt;settings.py&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Instructions
&lt;/h2&gt;

&lt;p&gt;Note: In the instructions below &lt;em&gt;.env.example&lt;/em&gt; and &lt;em&gt;.env&lt;/em&gt; should be saved in the same folder as &lt;em&gt;settings.py&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Install django-environ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django-environ
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add a .env.example file with these contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG=on
SECRET_KEY=your-secret-key
DATABASE_URL=psql://urser:urpassword@127.0.0.1:8458/database
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This file is an example for collaborators to follow when creating the .env file for their local environment.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; to your .gitignore file to prevent it from being added to the repository&lt;/p&gt;

&lt;p&gt;At the top of your &lt;em&gt;settings.py&lt;/em&gt; add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import environ
env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)
# reading .env file
environ.Env.read_env()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Change the DEBUG, SECRET_KEY and DATABASES lines in &lt;em&gt;settings.py&lt;/em&gt; to read their values from the environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG = env('DEBUG')
...
SECRET_KEY = env('SECRET_KEY')
...

DATABASES = {
    'default': env.db(),
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your can then add &lt;code&gt;.env.example&lt;/code&gt; and the the modified settings file to the Git repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .

git commit -m "Extracted secrets from the settings file"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For each deployment (staging, production, developer environment) you then:&lt;/p&gt;

&lt;p&gt;Create a .env file with the settings specific to that environment. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your desired DEBUG setting.&lt;/li&gt;
&lt;li&gt;The secret key.&lt;/li&gt;
&lt;li&gt;Your DATABASE connection string and credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Django has a utility function that the startproject command uses to generate the secret&lt;br&gt;
You can use it from the command line.&lt;br&gt;
With your virtual environment activated run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It will spit out a random string like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a1$%jr29f2(u^l=r1q1a2$sztg%x7%g8s@!ne#_(^5$woi#wi$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Assuming your DATABASE setting was&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'blog',
        'USER': 'dbuser',
        'PASSWORD': 'dbpassword',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your .env file would look like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG=on
SECRET_KEY=a1$%jr29f2(u^l=r1q1a2$sztg%x7%g8s@!ne#_(^5$woi#wi$
DATABASE_URL=psql://dbuser:dbpassword@127.0.0.1:5432/blog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note that there are not quotes around the values in the .env file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For your production deployment change  &lt;code&gt;DEBUG=on&lt;/code&gt; to &lt;code&gt;DEBUG=off&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;You can confirm that your app starts up successfully by running the Django Dev Server&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python manage runserver&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If there are any errors consult the &lt;a href="https://django-environ.readthedocs.io/en/latest/"&gt;Django-environ documentation&lt;/a&gt; for guidance.&lt;/p&gt;

</description>
      <category>django</category>
    </item>
    <item>
      <title>How to serve private media files with Django</title>
      <dc:creator>Joshua Masiko</dc:creator>
      <pubDate>Fri, 13 Mar 2020 17:35:56 +0000</pubDate>
      <link>https://dev.to/joshwizzy/how-to-serve-private-media-files-with-django-41e0</link>
      <guid>https://dev.to/joshwizzy/how-to-serve-private-media-files-with-django-41e0</guid>
      <description>&lt;p&gt;In this tutorial, we will create a barebones document manager with these features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logged in users can upload files.&lt;/li&gt;
&lt;li&gt;Superusers can view all files.&lt;/li&gt;
&lt;li&gt;Regular users can view only the files they uploaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These steps were performed on Ubuntu 18.04 LTS.&lt;/p&gt;

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

&lt;p&gt;First lets add the universe repository and install python3-venv.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo add-apt-repository universe
sudo apt install -y python3-venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Project and App
&lt;/h2&gt;

&lt;p&gt;Now create the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ~/.virtualenvs
python3 -m venv ~/.virtualenvs/docman
source ~/.virtualenvs/docman/bin/activate
pip install django
django-admin startproject docman
cd docman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create the database and tables by running migrate.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;After the migration process completes create an app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./manage.py startapp core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;

&lt;p&gt;Each document is associated with the user.&lt;br&gt;
We also track the date and time that the document was uploaded.&lt;br&gt;
By default documents will be sorted in reverse chronological order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/models.py

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.template.defaultfilters import slugify


class Document(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    file = models.FileField()
    created_by = models.ForeignKey(User, on_delete=models.PROTECT)
    created_at = models.DateTimeField(default=timezone.now)
    slug = models.SlugField(max_length=255, editable=False)

    class Meta:
        ordering = ['-created_at']

    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = slugify(self.title)

        return super(Document, self).save(*args, **kwargs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the app to the INSTALLED_APPS list in &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 plaintext"&gt;&lt;code&gt;# docman/settings.py

INSTALLED_APPS = [
...
    'core.apps.CoreConfig',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate migrations for the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./manage makemigrations core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the database to add the table for the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./manage migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The views
&lt;/h2&gt;

&lt;p&gt;The application functionality consists of 3 views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The default view displays a list of documents.&lt;/li&gt;
&lt;li&gt;A view to upload a document.&lt;/li&gt;
&lt;li&gt;A view to download a document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All views are restricted to logged-in users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/views.py

import os

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from django.views.generic.edit import CreateView
from django.http import FileResponse, HttpResponseForbidden, HttpResponse
from django.views import View
from django.urls import reverse
from django.conf import settings

from .models import Document


class DocumentList(LoginRequiredMixin, ListView):
    model = Document

    def get_queryset(self):
        queryset = Document.objects.all()
        user = self.request.user

        if not user.is_superuser:
            queryset = queryset.filter(
                created_by=user
            )

        return queryset


class DocumentCreate(LoginRequiredMixin, CreateView):
    model = Document
    fields = ['title', 'description', 'file']

    def get_success_url(self):
        return reverse('home')

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)


class DocumentDownload(View):
    def get(self, request, relative_path):
        document = get_object_or_404(Document, file=relative_path)
        if not request.user.is_superuser and document.created_by != request.user:
            return HttpResponseForbidden()
        absolute_path = '{}/{}'.format(settings.MEDIA_ROOT, relative_path)
        response = FileResponse(open(absolute_path, 'rb'), as_attachment=True)
        return response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Lets wire up the urls for the views.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# docman/urls.py

from django.contrib import admin
from django.urls import path
from django.contrib.auth import views as auth_views

from core.views import DocumentList, DocumentCreate, DocumentDownload

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', DocumentList.as_view(), name='home'),
    path('document-add/', DocumentCreate.as_view(), name='document-add'),
    path('media/&amp;lt;path:relative_path&amp;gt;', DocumentDownload.as_view(), name='document-download'),

    path('accounts/login/', auth_views.LoginView.as_view()),
    path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The templates
&lt;/h2&gt;

&lt;p&gt;Create a login template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/templates/registration/login.html

&amp;lt;form method="POST"&amp;gt;
    {% csrf_token %}
    {{form.as_p}}
    &amp;lt;button type="submit"&amp;gt;Login&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a base template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/templates/base.html

&amp;lt;h1&amp;gt;Document Manager&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;Logged in as {{user.get_full_name}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href="{% url "logout" %}"&amp;gt;Logout&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
{% block content %}
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The document list displays documents in reverse chronological order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/templates/core/document_list.html

{% extends "base.html" %}

{% block content %}
    &amp;lt;h2&amp;gt;Documents&amp;lt;/h2&amp;gt;
    &amp;lt;a href="{% url "document-add" %}"&amp;gt;Add Document&amp;lt;/a&amp;gt;
    &amp;lt;table width="50%"&amp;gt;
        &amp;lt;thead&amp;gt;
            &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;Title&amp;lt;/th&amp;gt;
                &amp;lt;th&amp;gt;Description&amp;lt;/th&amp;gt;
                &amp;lt;th&amp;gt;Created by&amp;lt;/th&amp;gt;
                &amp;lt;th&amp;gt;Created at&amp;lt;/th&amp;gt;
                &amp;lt;th&amp;gt;File&amp;lt;/th&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        {% for document in object_list %}
            &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;{{ document.title }}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;{{document.description}}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;{{document.created_by}}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;{{document.created_at}}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;&amp;lt;a href="{{document.file.url}}"&amp;gt;{{document.file.name}}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        {% endfor %}
    &amp;lt;/table&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Files are downloaded by clicking on the filename link.&lt;br&gt;
The &lt;code&gt;Add Document&lt;/code&gt; button links to the document upload page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/templates/core/document_form.html

{% extends 'base.html' %}
{% block content %}
&amp;lt;h1&amp;gt;Add Document&amp;lt;/h1&amp;gt;
&amp;lt;form method="POST" enctype="multipart/form-data"&amp;gt;
    {% csrf_token %}
    {{form.as_p}}
    &amp;lt;button type="submit"&amp;gt;Submit&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Django stores uploaded files on the local file system using paths relative to &lt;code&gt;MEDIA_ROOT&lt;/code&gt;&lt;br&gt;
Lets define media root in &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 plaintext"&gt;&lt;code&gt;# docman/settings.py

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also add a line at the bottom of &lt;code&gt;settings.py&lt;/code&gt; that sets the URL to redirect to when a user logs out&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# docman/settings.py

LOGOUT_REDIRECT_URL = '/'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The users
&lt;/h2&gt;

&lt;p&gt;We will create two users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A superuser who can view all uploaded documents.&lt;/li&gt;
&lt;li&gt;A regular user who can view only the documents they uploaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use the  &lt;a href="https://dev.tohttp://"&gt;django-createuser&lt;/a&gt; app to add a convenient management command for creating users.&lt;/p&gt;

&lt;p&gt;Install django-createuser&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django-createuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add django_createuser to your installed apps in &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 plaintext"&gt;&lt;code&gt;# docman/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'core.apps.CoreConfig',
    'django_createuser',  #new
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create the superuser.&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 createuser --email admin@example.com --first_name Administrator --is-superuser --password test1234 admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the regular user.&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 createuser --email user@example.com --first_name Test --last_name User --password test1234 user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the application
&lt;/h2&gt;

&lt;p&gt;We will use the Django the dev server.&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 runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 browser.&lt;/p&gt;

&lt;p&gt;You will get prompted to login using username and password.&lt;/p&gt;

&lt;p&gt;Login as the superuser using the username &lt;em&gt;admin&lt;/em&gt; and password &lt;em&gt;test1234&lt;/em&gt;.&lt;br&gt;
You should see the homepage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F11%2F4544a6905b-docman_home.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F11%2F4544a6905b-docman_home.PNG" alt="docman_home.PNG"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Click the &lt;em&gt;Add Document&lt;/em&gt; link and upload a document&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F11%2F0ba89fa89c-docman_upload.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F11%2F0ba89fa89c-docman_upload.PNG" alt="docman_upload.PNG"&gt;&lt;/a&gt; &lt;br&gt;
Then:&lt;br&gt;
Logout from the superuser account.&lt;br&gt;
Login as the regular user using the username &lt;em&gt;user&lt;/em&gt; and password &lt;em&gt;test1234&lt;/em&gt;.&lt;br&gt;
The document uploaded by the superuser will not be visible in the list.&lt;br&gt;
Upload a document as the regular user.&lt;/p&gt;

&lt;p&gt;If you login as the superuser both documents are displayed in the list.&lt;br&gt;
You should be able to download both documents as the superuser by clicking on the links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing access control
&lt;/h2&gt;

&lt;p&gt;Copy the both document links and paste them in a text editor&lt;/p&gt;

&lt;h4&gt;
  
  
  Test access for logged out users
&lt;/h4&gt;

&lt;p&gt;Logout and try downloading the files using the links you copied.&lt;br&gt;
Access will be denied&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F11%2Fa4b7a0d2e8-docman_accessdenied.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl2earn.nyc3.digitaloceanspaces.com%2Fl2earn%2Fimages%2Fuploads%2F2020%2F03%2F11%2Fa4b7a0d2e8-docman_accessdenied.PNG" alt="docman_accessdenied.PNG"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Test access to superuser file for regular user.
&lt;/h4&gt;

&lt;p&gt;Login as the regular user then try downloading the superuser file using the link you copied.&lt;br&gt;
Access will be denied&lt;/p&gt;

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

&lt;p&gt;The setup described in this article is not recommended for production.&lt;br&gt;
Serving media files is best handled by a web server like NGINX or a CDN backed Object Storage service like AWS S3.&lt;/p&gt;

&lt;p&gt;A future article will evolve our document manager to use NGINX and Object Storage.&lt;/p&gt;

</description>
      <category>django</category>
    </item>
  </channel>
</rss>
