Django DEV mode
Download and install Postgres
https://postgresapp.com/downloads.html
By default postgres use the port 5432.
By default postgres use the current user.
Example with user = docuser
CREATE USER docuser WITH ENCRYPTED PASSWORD 'password';
ALTER ROLE docuser SET client_encoding TO 'utf8';
ALTER ROLE docuser SET default_transaction_isolation TO 'read committed';
\du
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
Arnaud | Superuser, Create role, Create DB | {}
docuser | | {}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
Example with database = blog
CREATE DATABASE blog;
\l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
Arnaud | Arnaud | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
blog | Arnaud | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(5 rows)
GRANT ALL PRIVILEGES ON DATABASE blog TO docuser;
\l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
Arnaud | Arnaud | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
blog | Arnaud | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =Tc/Arnaud +
| | | | | Arnaud=CTc/Arnaud +
| | | | | docuser=CTc/Arnaud
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(5 rows)
\c blog
You are now connected to database "blog" as user "Arnaud".
\dt
Did not find any relations.
pip install Django
Example with project = django-postgresql
django-admin startproject django-postgresql
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Example with:
- name = blog
- user = docuser
- password = 123456
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'blog',
'USER': 'docuser',
'PASSWORD': '123456',
'HOST': 'localhost',
'PORT': 5432,
}
}
python manage.py migrate
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
\dt
List of relations
Schema | Name | Type | Owner
--------+----------------------------+-------+---------
public | auth_group | table | docuser
public | auth_group_permissions | table | docuser
public | auth_permission | table | docuser
public | auth_user | table | docuser
public | auth_user_groups | table | docuser
public | auth_user_user_permissions | table | docuser
public | django_admin_log | table | docuser
public | django_content_type | table | docuser
public | django_migrations | table | docuser
public | django_session | table | docuser
(10 rows)
Example with
superuser = admin
email = admin@halia.ca
password = password
python manage.py createsuperuser
Username (leave blank to use 'arnaud'): admin
Email address: admin@halia.ca
Password:
Password (again):
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
February 13, 2022 - 05:07:48
Django version 3.1.7, using settings 'blog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Example with
url = http://127.0.0.1:8000/admin (default)
Case when you want to change the default admin url.
blog/urls.py
urlpatterns
Example with route = django-admin
urlpatterns = [
path('django-admin/', admin.site.urls),
]
Example with app = posts
python manage.py startapp posts
INSTALLED_APPS
settings.py
posts
at the end of INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts',
]
posts/models.py
from django.contrib.auth import get_user_model
from django.db import models
from django.template.defaultfilters import slugify
User = get_user_model()
class BlogPost(models.Model):
"""New BlogPost model."""
title = models.CharField(max_length=255, unique=True, verbose_name="Title")
slug = models.SlugField(max_length=255, unique=True, blank=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True,
blank=True)
last_updated = models.DateTimeField(auto_now=True)
created_on = models.DateField(blank=True, null=True)
published = models.BooleanField(default=False, verbose_name="Published")
content = models.TextField(blank=True, verbose_name="Content")
class Meta:
"""
Override Meta class.
ordering: ordering on field created_on
verbose_name: Application name is Article
"""
ordering = ['-created_on']
verbose_name = 'Post'
def __str__(self):
"""Display title in Django admin."""
return self.title
def save(self, *args, **kwargs):
"""Create a slug with slugify based on title."""
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
@property
def author_or_default(self) -> str:
"""Get author or return unknown."""
return self.author.username if self.author else "unknown"
Before Django 3.2, the primary key field that Django would add was of type 'django.db.models.AutoField'. Since Django 3.2 the default has been changed to 'django.db.models.BigAutoField'.
In order to get rid of the warning you need to set the DEFAULT_AUTO_FIELD in the settings to the field you prefer for primary keys. If you rather keep the Django 3.1 default you should set the auto-field value to 'django.db.models.AutoField'. This can also prevent some unwanted migrations in the future.
You can either set the value in your settings.py
:
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
Or set it per app in the posts/apps.py
file:
from django.apps import AppConfig
class PostsConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField'
name = 'posts'
posts/admin
from django.contrib import admin
from posts.models import BlogPost
class BlogPostAdmin(admin.ModelAdmin):
"""BlogPostAdmin."""
list_display = ("title", "published", "created_on", "last_updated")
list_editable = ("published", )
admin.site.register(BlogPost, BlogPostAdmin)
Remember that BlogPost has been renamed with
Post
verbose_name = 'Post'
Django add as
at the end ofPost
At the root of project
templates
directorycore.html
fileTEMPLATES
in settings.py
settings.py
DIRS
in TEMPLATES
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [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',
],
},
},
]
core.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block title %}
{% endblock %}
</head>
<body>
<section id="blog">
{% block content %}
{% endblock %}
</section>
</body>
</html>
posts/views.py
to list all postsposts/views.py
from django.shortcuts import render
from django.views.generic import ListView
from posts.models import BlogPost
class ListBlogPosts(ListView):
"""ListBlogPosts."""
model = BlogPost
context_object_name = "posts"
posts/urls.py
to list all postsposts/urls.py
filefrom django.urls import path
from posts.views import ListBlogPosts
app_name = "posts"
urlpatterns = [
path('', ListBlogPosts.as_view(), name="home"),
]
blog/settings.py
by including posts.urls
blog/urls.py
path('blog/', include('posts.urls'))
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('posts.urls'))
]
posts/templates/posts
directoryblogpost_list.html
file{% extends 'core.html' %}
{% block title %}
<title>Home blog</title>
{% endblock %}
{% block content %}
<h1>Test blog</h1>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<h5>published by <i>{{ post.author_or_default }}</i> on {{ post.created_on|date:'j F Y' }}</h5>
<p>{{ post.content|safe|truncatewords:50 }}</p>
</article>
{% endfor %}
{% endblock %}
If a user registered on the website is authenticated then he is able to see all post, publiched and not published.
All other users will only see published posts.
class ListBlogPosts(ListView):
"""ListBlogPosts."""
model = BlogPost
context_object_name = "posts"
def get_queryset(self):
queryset = super().get_queryset()
if self.request.user.is_authenticated:
return queryset
return queryset.filter(published=True)
posts/views.py
to create a postposts/views.py
class CreateBlogPost(CreateView):
"""CreateBlogPost."""
model = BlogPost
template_name = "posts/blogpost_create.html"
fields = ["title", "content"]
posts/urls.py
to create a postposts/urls.py
filefrom django.urls import path
from posts.views import ListBlogPosts, CreateBlogPost
app_name = "posts"
urlpatterns = [
path('', ListBlogPosts.as_view(), name="home"),
path('create/', CreateBlogPost.as_view(), name="create-post"),
]
posts/templates/posts
directoryblogpost_create.html
file{% extends 'core.html' %}
{% block title %}
<title>Create a post</title>
{% endblock %}
{% block content %}
<h1>Create a post</h1>
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="Create">
</form>
{% endblock %}
posts.models.py
class BlogPost(models.Model):
"""New BlogPost model."""
...
@staticmethod
def get_absolute_url() -> Optional[str]:
"""
Get the absolute url for redirection.
see: posts.urls.py
app_name:name => posts:home
"""
return reverse("posts:home")
url: http://127.0.0.1:8000/blog/update/<post.slug>
posts/views.py
to update a postposts/views.py
class UpdateBlogPost(UpdateView):
"""UpdateBlogPost."""
model = BlogPost
template_name = "posts/blogpost_update.html"
fields = ["title", "content", "published"]
posts/urls.py
to update a postposts/urls.py
filefrom django.urls import path
from posts.views import ListBlogPosts, CreateBlogPost, UpdateBlogPost
app_name = "posts"
urlpatterns = [
path('', ListBlogPosts.as_view(), name="home"),
path('create/', CreateBlogPost.as_view(), name="create-post"),
path('update/<str:slug>/', UpdateBlogPost.as_view(), name="update-post"),
]
posts/templates/posts
directoryblogpost_update.html
file{% extends 'core.html' %}
{% block title %}
<title>Update a post</title>
{% endblock %}
{% block content %}
<h1>Update a post</h1>
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="Update">
</form>
{% endblock %}
url: http://127.0.0.1:8000/blog/<post.slug>
posts/views.py
to show a postposts/views.py
class ShowBlogPost(DetailView):
"""ShowBlogPost."""
model = BlogPost
context_object_name = "post"
template_name = "posts/blogpost_show.html"
posts/urls.py
to show a postposts/urls.py
filefrom django.urls import path
from posts.views import ListBlogPosts, CreateBlogPost, UpdateBlogPost, \
ShowBlogPost
app_name = "posts"
urlpatterns = [
path('', ListBlogPosts.as_view(), name="home"),
path('create/', CreateBlogPost.as_view(), name="create-post"),
path('update/<str:slug>/', UpdateBlogPost.as_view(), name="update-post"),
path('<str:slug>/', ShowBlogPost.as_view(), name="show-post"),
]
posts/templates/posts
directoryblogpost_show.html
file{% extends 'core.html' %}
{% block title %}
<title>{{ post.title }} </title>
{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<h5>published by <i>{{ post.author_or_default }}</i> on {{ post.created_on|date:'j F Y' }}</h5>
<br>
<div>{{ post.content|linebreaks|safe }}</div>
</article>
{% endblock %}
url: http://127.0.0.1:8000/blog/delete/<post.slug>
posts/views.py
to delete a postposts/views.py
class DeleteBlogPost(DeleteView):
"""ShowBlogPost."""
model = BlogPost
context_object_name = "post"
template_name = "posts/blogpost_delete.html"
success_url = reverse_lazy("posts:home")
posts/urls.py
to delete a postposts/urls.py
filefrom django.urls import path
from posts.views import ListBlogPosts, CreateBlogPost, UpdateBlogPost, \
ShowBlogPost, DeleteBlogPost
app_name = "posts"
urlpatterns = [
path('', ListBlogPosts.as_view(), name="home"),
path('create/', CreateBlogPost.as_view(), name="create-post"),
path('update/<str:slug>/', UpdateBlogPost.as_view(), name="update-post"),
path('delete/<str:slug>/', DeleteBlogPost.as_view(), name="delete-post"),
path('<str:slug>/', ShowBlogPost.as_view(), name="show-post"),
]
posts/templates/posts
directoryblogpost_delete.html
file{% extends 'core.html' %}
{% block title %}
<title>{{ post.title }}</title>
{% endblock %}
{% block content %}
<h1>Delete {{ post.title }}</h1>
<form method="POST">
{% csrf_token %}
<p>Are you sure you want to delete "{{ post.title }}"?</p>
<input type="submit" value="Yes, Delete">
</form>
{% endblock %}
CreateBlogPost
@method_decorator(login_required, name='dispatch')
class CreateBlogPost(CreateView):
"""CreateBlogPost."""
model = BlogPost
template_name = "posts/blogpost_create.html"
fields = ["title", "content"]
@method_decorator(login_required, name='dispatch')
class UpdateBlogPost(UpdateView):
"""UpdateBlogPost."""
model = BlogPost
template_name = "posts/blogpost_update.html"
fields = ["title", "content", "published"]
@method_decorator(login_required, name='dispatch')
class DeleteBlogPost(DeleteView):
"""ShowBlogPost."""
model = BlogPost
context_object_name = "post"
template_name = "posts/blogpost_delete.html"
success_url = reverse_lazy("posts:home")
home
and create post
posts.templates.core.html
<nav>
section<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block title %}
{% endblock %}
</head>
<body>
<nav>
<a href="{% url 'posts:home' %}">Home</a>
{% if request.user.is_authenticated %}
<a href="{% url 'posts:create-post' %}">Create a post</a>
{% endif %}
</nav>
<section id="blog">
{% block content %}
{% endblock %}
</section>
</body>
</html>
update post
and delete post
posts.templates.posts.blogpost_list.html
{% extends 'core.html' %}
{% block title %}
<title>Home blog</title>
{% endblock %}
{% block content %}
<h1>Test blog</h1>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
{% if request.user.is_authenticated %}
<div>
<a href="{% url 'posts:update-post' slug=post.slug %}">Update post</a>
<a href="{% url 'posts:delete-post' slug=post.slug %}">Delete post</a>
</div>
{% endif %}
<h5>published by <i>{{ post.author_or_default }}</i> on {{ post.created_on|date:'j F Y' }}</h5>
<p>{{ post.content|safe|truncatewords:50 }}</p>
<form action="{% url 'posts:show-post' slug=post.slug %}">
<button>Read post</button>
</form>
</article>
{% endfor %}
{% endblock %}