This post describes how to create a Django project on Ubuntu as of December 2019.
Create Django project
Install dependencies:
sudo apt install python3.6; sudo apt install python3-venv; sudo apt install python3-pip
Create and activate a Python 3 virtual environment:
python3.6 -m venv venv source venv/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==2.1.13 pip install Pillow==6.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
Configure settings
Open base.py:
nano config/settings/base.py
Adjust the directory to reflect new location depth of settings. Take secret key offline.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
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': [os.path.join(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': os.path.join(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.
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

python manage.py makemigrations; python manage.py migrate
Push changes
git add .; git commit -m "Project structure"; git push origin master
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 'test' %}">Test Page</a> <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/home.html
{% extends 'base.html' %} {% block meta %} {% endblock %} {% block title %}Home{% endblock %} {% block entry_title %}Home{% endblock %} {% block content %} {% endblock %}
nano project_slug/templates/test.html
{% extends 'base.html' %} {% block title %}Test page{% endblock %} {% block content %} <p>Test content goes here. I used <a href="https://github.com/juzraai/bootstrap4-test-page">this page</a>.</p> {% 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('', TemplateView.as_view(template_name='home.html'), name='home'), path('test/', TemplateView.as_view(template_name='test.html'), name='test'), ... ] + 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