This post describes how to create a Django project on Ubuntu as of December 2019.
Create Django project
Install dependencies:
sudo apt install python3.9;
sudo apt install python3-venv;
sudo apt install python3-pip
sudo apt install libpq-dev
Create and activate a Python 3 virtual environment:
python3.9 -m venv env
source env/bin/activate
Next, install Python dependencies. In this case, when I installed more recent versions of django than 2.1.*, it caused problems later on.
pip install django==3.2.15
pip install Pillow==9.2.0
Create a project using Django’s default template
django-admin startproject project_slug
cd project_slug
Establish the Django project as a GitHub repository
Create a README.md markdown file:
nano README.md
# Project title
Description
## Requirements & Dependencies
- Requirement 1
- Requirement 2
- Requirement 3
## How to use
...
## Licenses & Credits
Add official python .gitignore
curl -o .gitignore https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
It is best practice to save dependencies in a requirements.txt file. This makes it possible to clone the project easily from git.
Store dependencies
pip freeze > requirements.txt
Verify that the pkg-resources package is not in requirements.txt (this is an Ubuntu bug that causes problems later)
Create a new GitHub repository. Don’t add a .gitignore, README, or license per GitHub’s documentation. Make it private to avoid revealing server information.

Install dependencies
sudo apt install git
Set up an SSH key for your GitHub for password-less development.
ssh-keygen -t rsa -b 4096;
cat ~/.ssh/id_rsa.pub
Copy the full contents of the file to the SSH settings in your GitHub account.
Push to master
git init;
git add .;
git commit -m "Initial commit";
git remote add origin git@github.com:nathankjer/project-slug.git;
git push origin master
Setup
Move configurations to config directory.
mkdir config/
mkdir config/settings/
mv project_slug/settings.py config/settings/base.py
touch config/__init__.py
touch config/settings/__init__.py
Move wsgi and urls into config.
mv project_slug/urls.py config/urls.py
mv project_slug/wsgi.py config/wsgi.py
mv project_slug/asgi.py config/asgi.py
Delete the project_slug directory:
rm -r project_slug
Configure settings
Open base.py:
nano config/settings/base.py
Adjust the directory to reflect new location depth of settings. Take secret key offline.
import environ
...
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
# Remove. This is not a base setting.
# SECRET_KEY = "django-insecure-xxxxx"
Turn off debugging. We can turn it on again in certain environments, but this should be the default.
# This should always be false since it is defined to be true in the development environment
DEBUG = False
# Other security settings
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = "DENY"
ALLOWED_HOSTS = []
DJANGO_APPS = [
...
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
...
]
THIRD_PARTY_APPS = [
]
LOCAL_APPS = [
]
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
Add context processors.
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
...
'DIRS': [str(BASE_DIR / "project_slug/templates")],
...
OPTIONS': {
'context_processors': [
...
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
# Remove me
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
Now close the file by holding Ctrl + X, then hitting Y and Enter to save.
Let’s create settings for our local development environment.
nano config/settings/development.py
from config.settings.base import *
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'project_slug',
'USER': 'postgres',
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': '127.0.0.1',
'PORT': '5432',
'ATOMIC_REQUESTS': True,
'CONN_MAX_AGE': 60
}
}
#
# OR
#
# DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.sqlite3",
# "NAME": BASE_DIR / "db.sqlite3",
# }
# }
DEBUG = True
ALLOWED_HOSTS = ['localhost','0.0.0.0','127.0.0.1']
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "",
}
}
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_TIMEOUT = 5
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = "/static/"
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "project_slug/static/"),
]
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "project_slug/media/")
Now close the file.
Create Database
First, install the psycopg2-binary, and set up a local postgresql database.
pip install psycopg2-binary
sudo apt install postgresql
sudo -u postgres psql
\password postgres
\q
sudo service postgresql restart
Next, install pgadmin. Follow the instructions here. Then launch pgAdmin 4 and create a server.



Create a database for the project:

Add environment variables
Open the bash profile:
nano ~/.bashrc
# ~/.bash_profile in macOS
Add the following lines:
export DJANGO_SECRET_KEY="..."
export DJANGO_SETTINGS_MODULE="config.settings.development"
export DB_PASSWORD="..." # Password for postgres database, if used
export EMAIL_HOST_USER="username@gmail.com"
export EMAIL_HOST_PASSWORD="..." # Generated at https://security.google.com/settings/security/apppasswords
You may need to run the following in order to see the variables:
source ~/.bashrc
# source ~/.bash_profile in macOS
Configure global settings
Open manage.py:
nano manage.py
It’s convenient to have a base set of settings, plus different configurations on top of it for different environments.
# Change settings path
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.development')
) from exc
# Allow apps in project_slug folder
current_path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(current_path, 'project_slug'))
execute_from_command_line(sys.argv)
nano config/wsgi.py
import os
import sys
from django.core.wsgi import get_wsgi_application
app_path = os.path.abspath(
os.path.join(os.path.dirname(os.path.abspath(__file__)),os.pardir)
)
sys.path.append(os.path.join(app_path, "project_slug"))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.development')
application = get_wsgi_application()
nano config/urls.py
...
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Now let’s see if it’s working. We can leave a browser tab open while we apply changes in real-time.
python manage.py runserver

Push changes
git add .;
git commit -m "Project structure";
git push origin master
Create core app
I have found it to be convenient to create an app called “core”, and assign the Home view to it.
python manage.py startapp core
mv core/ project_slug/
Clear contents of admin.py and models.py files.
Set views.py to the following:
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
class HomeView(LoginRequiredMixin, TemplateView):
template_name = "core/home.html"
Set urls.py to the following:
from django.urls import path
from core.views import HomeView
urlpatterns = [
path("", HomeView.as_view(), name="home"),
]
Add core app to config/settings/base.py:
LOCAL_APPS = [
'core',
...
]
Create views
mkdir project_slug/templates;
nano project_slug/templates/base.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<!--[if IE]>
<link rel="shortcut icon" href="{% static "images/favicon.png" %}">
<![endif]-->
{% block meta %}{% endblock %}
<link rel="icon" href="{% static "images/favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
<link rel="stylesheet" href="{% static "css/animate.min.css" %}">
<link rel="stylesheet" href="{% static "css/hover.min.css" %}">
<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static "css/style.css" %}">
<title>{% block title %}{% endblock %}</title>
<style>{% block style %}{% endblock %}</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">
{{ request.site.name }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="nav navbar-nav ml-auto">
{% block navbar %}
{% endblock %}
{% if user.is_authenticated %}
<li class="nav-item mx-1">
<div class="btn-group" role="group">
<button class="btn btn-light dropdown-toggle" data-toggle="dropdown" id="account_dropdown" type="button">
<i class="fas fa-user"></i> {{ user.username }}
</button>
<div aria-labelledby="account_dropdown" class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="{% url 'user_detail' user.username %}">My Account</a>
<a class="dropdown-item" href="{% url 'account_change_password' %}">Change Password</a>
<a class="dropdown-item" href="{% url 'account_email' %}">Change E-Mail</a>
<a class="dropdown-item" href="{% url 'account_logout' %}">Log Out</a>
{% if user.is_staff %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a>
{% endif %}
</div>
</div>
</li>
{% else %}
<li class="nav-item mx-1">
<a class="nav-link" href="{% url 'account_login' %}">Log in</a>
</li>
{% endif %}
</ul>
</div>
</nav>
<main class="pb-4 pt-5" id="main">
<div class="row">
{% block feature_header %}{% endblock %}
</div>
<div class="row">
<div class="container mt-5">
{% block content %}{% endblock %}
</div>
</div>
</main>
<footer class="footer">
<div class="float-left mx-5">
<ul class="list-inline">
{% for page in footer_list %}
<li class="list-inline-item"><a class="text-muted" href=""></a></li>
{% endfor %}
</ul>
</div>
<small class="float-right mx-5 text-muted">
Copyright © {% now 'Y' %} <a href="/" class="text-muted">{{ request.site.name }}</a>
</small>
</footer>
<script src="{% static "js/jquery.min.js" %}"></script>
<script src="{% static "js/popper.min.js" %}"></script>
<script src="{% static "js/bootstrap.min.js" %}"></script>
<script src="{% static "js/font-awesome.min.js" %}"></script>
<script src="{% static "js/custom.js" %}"></script>
<script>{% block script %}{% endblock %}</script>
</body>
</html>
nano project_slug/templates/core/home.html
{% extends 'base.html' %}
{% block meta %}
{% endblock %}
{% block title %}Home{% endblock %}
{% block entry_title %}Home{% endblock %}
{% block content %}
{% endblock %}
Make forms Bootstrap 4 compatible with Crispy forms:
pip install --upgrade django-crispy-forms
nano config/settings/base.py
THIRD_PARTY_APPS = [
'crispy_forms',
]
CRISPY_TEMPLATE_PACK = 'bootstrap4'
nano project_slug/templates/create.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Create {{ form|form_verbose_name }}{% endblock %}
{% block content %}
<div class="container mt-5">
<h1 class="mb-3">Create {{ form|form_verbose_name }}</h1>
<form method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="save btn btn-primary">Save</button>
</form>
</div>
{% endblock %}
nano project_slug/templates/update.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Edit "{{ object }}"{% endblock %}
{% block content %}
<div class="container mt-5">
<h1 class="mb-3">Edit "{{ object }}"</h1>
<form method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="save btn btn-primary">Save</button><a href="../delete/" class="btn btn-outline-danger mx-1">Delete</a>
</form>
</div>
{% endblock %}
nano project_slug/templates/delete.html
{% extends 'base.html' %}
{% block title %}Delete "{{ object }}"{% endblock %}
{% block content %}
<div class="container mt-5">
<h1 class="mb-3">Delete "{{ object }}"</h1>
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<button type="submit" class="save btn btn-danger">Confirm</button>
</form>
</div>
{% endblock %}
Error pages & account forms
Copy error page forms from the cookiecutter-django repository.
curl -o project_slug/templates/403.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/403.html;
curl -o project_slug/templates/404.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/404.html;
curl -o project_slug/templates/500.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/500.html
Prepare static
mkdir project_slug/media
mkdir project_slug/media/images
mkdir project_slug/static
mkdir project_slug/static/css
mkdir project_slug/static/js
touch project_slug/static/css/style.css
touch project_slug/static/js/custom.js
Add the default profile picture:
curl -o project_slug/media/images/user.jpg https://i.stack.imgur.com/34AD2.jpg
nano project_slug/static/css/style.css
html {
position:relative;
min-height:100%;
}
body {
margin-bottom:60px;
}
body, .badge {
font-size:18px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
a {
text-decoration: none!important;
}
.card-img-top {
width: 100%;
height: 200px;
object-fit: cover;
}
.btn {
min-width:78px;
}
.row {
margin:0;
}
.footer {
position:absolute;
bottom:0;
width:100%;
height:60px;
line-height:60px;
overflow:hidden;
border-top:1px solid rgba(0,0,0,0.1);
}
.post-img-wrapper {
max-height:500px;
width:100%;
overflow:hidden;
background-color:#000;
}
.post-img {
width: 100%;
}
Download bootstrap dependencies
curl -o project_slug/static/css/bootstrap.min.css https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css
curl -o project_slug/static/js/jquery.min.js https://code.jquery.com/jquery-3.3.1.slim.min.js
curl -o project_slug/static/js/popper.min.js https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js
curl -o project_slug/static/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js
Install third party libraries
curl -o project_slug/static/js/font-awesome.min.js https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/js/all.min.js
nano config/urls.py
...
from django.views.generic import TemplateView
from django.views import defaults as default_views
urlpatterns = [
path('', include('core.urls')),
...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
urlpatterns += [
path("400/",default_views.bad_request,kwargs={"exception": Exception("Bad Request")},),
path("403/",default_views.permission_denied,kwargs={"exception": Exception("Permission Denied")},),
path("404/",default_views.page_not_found,kwargs={"exception": Exception("Page not Found")},),
path("500/", default_views.server_error),
]
User authentication using django-allauth
pip install django-allauth
nano config/settings/base.py
DJANGO_APPS = [
...
'django.contrib.auth', # Make sure these are here
...
'django.contrib.sites',
'django.contrib.messages',
]
THIRD_PARTY_APPS = [
...
'allauth',
'allauth.account',
'allauth.socialaccount',
)
MIDDLEWARE = [
...
'django.contrib.sites.middleware.CurrentSiteMiddleware',
]
...
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
SITE_ID = 1
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS =1
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS =1
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 86400 # 1 day in seconds
ACCOUNT_LOGOUT_REDIRECT_URL ='/accounts/login/'
LOGIN_REDIRECT_URL = '/'
Now let’s double check that the site has the correct ID (which should be 1).
python manage.py shell
from django.contrib.sites.models import Site
>>> Site.objects.all().delete()
>>> Site.objects.create(id=1,name='Site name',domain='127.0.0.1:8000')
>>> quit()
Copy account forms from the cookiecutter-django repository.
mkdir project_slug/templates/account/
curl -o project_slug/templates/account/account_inactive.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/account_inactive.html;
curl -o project_slug/templates/account/base.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/base.html;
curl -o project_slug/templates/account/email.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/email.html;
curl -o project_slug/templates/account/email_confirm.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/email_confirm.html;
curl -o project_slug/templates/account/login.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/login.html;
curl -o project_slug/templates/account/logout.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/logout.html;
curl -o project_slug/templates/account/password_change.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/password_change.html;
curl -o project_slug/templates/account/password_reset.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/password_reset.html;
curl -o project_slug/templates/account/password_reset_done.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/password_reset_done.html;
curl -o project_slug/templates/account/password_reset_from_key.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/password_reset_from_key.html;
curl -o project_slug/templates/account/password_reset_from_key_done.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/password_reset_from_key_done.html;
curl -o project_slug/templates/account/password_set.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/password_set.html;
curl -o project_slug/templates/account/signup.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/signup.html;
curl -o project_slug/templates/account/signup_closed.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/signup_closed.html;
curl -o project_slug/templates/account/verification_sent.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/verification_sent.html;
curl -o project_slug/templates/account/verified_email_required.html https://raw.githubusercontent.com/pydanny/cookiecutter-django/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/templates/account/verified_email_required.html
nano config/urls.py
...
from django.urls import path, include
...
urlpatterns = [
...
path('accounts/', include('allauth.urls')),
...
]
Custom user
python manage.py startapp users
mv users/ project_slug/
nano config/settings/base.py
LOCAL_APPS = [
'users',
]
...
AUTH_USER_MODEL = 'users.User'
nano project_slug/users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
# Auto Fields
id = models.AutoField(primary_key=True)
# Required
name = models.CharField(max_length=255)
email = models.EmailField(unique=True)
# Optional
avatar = models.ImageField(upload_to='images',default='images/user.jpg',null=True,blank=True)
bio = models.TextField(null=True, blank=True)
nano project_slug/users/admin.py
from django.contrib import admin
from .models import User
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
pass
nano config/urls.py
from django.urls import path, include
...
urlpatterns = [
...
path('users/', include('users.urls')),
...
]
nano project_slug/users/urls.py
from django.urls import path
from . import views
urlpatterns = [
# Users
path('<slug:username>/update/', views.UserUpdateView.as_view(), name='user_update'),
path('<slug:username>/', views.UserDetailView.as_view(), name='user_detail'),
]
nano project_slug/users/views.py
from django.views.generic import DetailView
from django.views.generic.edit import UpdateView
from django.shortcuts import get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from .models import User
from .forms import UserForm
class UserDetailView(DetailView):
model = User
template_name = 'users/user_detail.html'
def get_object(self):
return get_object_or_404(User, username=self.kwargs['username'])
class UserUpdateView(LoginRequiredMixin, UpdateView):
model = User
form_class = UserForm
login_url = '/accounts/login/'
template_name = 'update.html'
def get_object(self):
return get_object_or_404(User, username=self.kwargs['username'])
def get_success_url(self):
return reverse('user_detail', kwargs={'username': self.object.username})
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj != self.request.user and not self.request.user.is_staff:
raise Http404("You are not allowed to edit this")
return super(UserUpdateView, self).dispatch(request, *args, **kwargs)
nano project_slug/users/forms.py
from django import forms
from .models import *
class UserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ['name','bio','avatar']
mkdir project_slug/templates/users;
nano project_slug/templates/users/user_detail.html
{% extends 'base.html' %}
{% block title %}{{ object }}{% endblock %}
{% block navbar %}
{% if user.is_staff or object.created_by == user or object == user %}
<li class="nav-item"><a href="update/" class="btn btn-light mx-1"><i class="fas fa-edit"></i> Edit</a></li>
{% endif %}
{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="mb-3 text-center">
{% if object.avatar %}
<img src="{{ object.avatar.url }}" class="img-thumbnail rounded-circle" style="height:100px;width:100px;">
{% endif %}
<p>
{% if object.name %}
<b>{{ object.name }}</b>
<span class="text-muted">|</span>
{% endif %}
<a href="{% url 'user_detail' object.username %}">@{{ object.username }}</a>
</p>
{% if object.bio %}
<p class="lead">{{ object.bio }}</p>
{% endif %}
</div>
<p><a href="{% url 'home' %}">← Home</a></p>
</div>
{% endblock %}
python manage.py makemigrations;
python manage.py migrate;
python manage.py createsuperuser
Go to http://127.0.0.1:8000/admin and log in.

Version control with django-reversion
pip install django-reversion
nano config/settings/base.py
THIRD_PARTY_APPS = [
'reversion',
]
MIDDLEWARE = [
...
'reversion.middleware.RevisionMiddleware',
]
Store dependencies to reflect updated third-party apps.
pip freeze > requirements.txt
Commit changes.
git add .;
git commit -m "Third-party apps";
git push origin master
Stop the server with Control+C and deactivate:
Deactivate the virtual environment by typing in the following:
deactivate