0% found this document useful (0 votes)
23 views

Guide to Django - Building Advanced Websites With Python

This document serves as a comprehensive guide for configuring, developing, and optimizing web applications using the Django framework. It covers the basics of Django, including its purpose, common tools, and necessary knowledge for effective use, as well as detailed instructions for setting up a development environment. The guide also includes practical steps for creating a virtual environment and using the PyCharm IDE for Django development.

Uploaded by

Manan Raja
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views

Guide to Django - Building Advanced Websites With Python

This document serves as a comprehensive guide for configuring, developing, and optimizing web applications using the Django framework. It covers the basics of Django, including its purpose, common tools, and necessary knowledge for effective use, as well as detailed instructions for setting up a development environment. The guide also includes practical steps for creating a virtual environment and using the PyCharm IDE for Django development.

Uploaded by

Manan Raja
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 426

STEP BY STEP TO PROFESSIONALISM:

CONFIGURATION, DEVELOPMENT, AND


OPTIMIZATION OF WEB APPLICATIONS WITH
DJANGO

Guide to Django:
Building Advanced
Websites with Python

YOUR KEY TO THE WORLD OF DJANGO POSSIBILITIES -


CREATE MODERN WEBSITES EASILY AND EFFICIENTLY
© Volodymyr Zadorozhnyi
Django for Beginners

A few words about Django


So, what is Django?
In the world of programming, it's one of the popular, if
not the most popular, frameworks for building websites
and web applications.
You might be wondering, what do YouTube, Google,
Dropbox, and Instagram have in common?
Correct! They are all built using the Django framework!
You might be asking, what exactly is this framework?
A framework is a software structure, a framework
consisting of multiple components, where common
problems are already solved, and based on which a
programmer can write their own code and ensure the
server-side operation of the website.
For example, many websites require forms for filling in
data, an admin panel, logging procedures,
authentication, as well as the need to work with
databases, and so on.
All these tasks are essentially repetitive.
By solving one task in one place, you can solve the
same task in another place in the same way.
So, a framework solves all these and other tasks.
If you go to the documentation on the main Django
framework homepage,
docs.djangoproject.com/en/4.1/
you will see a complete list of tasks that Django has
already solved, and accordingly, you can use all these
functions.

Common web application tools


Django offers multiple tools commonly needed in the development of web applications:

• Authentication: Overview | Using the authentication system | Password management | Customizing


authentication | API Reference

• Caching

• Logging

• Sending emails

• Syndication feeds (RSS/Atom)

• Pagination

• Messages framework

• Serialization

• Sessions

• Sitemaps

• Static files management

• Data validation

So, what is Django?


For instance, there are tools here that allow you to
solve problems such as caching, logging, sending
emails, and so on.
It's also worth noting that the framework provides us
with coding rules that we must adhere to:
To fully work with this framework, the following
knowledge is necessary:

1. Python language and its Object-Oriented


Programming (OOP).
2. Template handling.
3. Basic knowledge of working with SQL queries.
4. Regular expressions.
5. Fundamentals of ORM (Object-Relational Mapping),
i.e., interacting with a database through class
models.
Table of Contents
Introduction

■ Purpose of the Django Framework........... ............... 5

Preliminary stage

• Running a web-server at home for educational purposes................................... ........... 6


• C re atin g a virtu a.I e nviro nm ent......... ............. 7
• PyCh a rm in tegrated d evel o p m e nt envi ron ment..............................................................16
• Installing the Django framework.............................. 22

Basics of Django

• C reatin g our project................. 24


■ Mo del-View-Tempi ate (MVT) pattern................................................................................28
• Views (creating a homepage handler)................ 32
• Worki ng with re gu I a r exp ress io ns ......... 48
• Handling GET and POST requests........................ 51
• Handling exceptions In server requests........ . ................................................................ 55
* Creating 301 and 302 redirects .............................. SO
• Data models. Migrations ........................................................................................... 65
■ Templates............ . ............................................................................................. 74
* Creating an Admin panel......... . ..................................................................................vv^. 87
■ Co nnect i ng stat ic fi I es .................................... 103
• Generating URLs in templates........ ..................................................... 123
• Creating relationships between data models ....................................................... 1Tvr. 136
• Admin panel. Registering the Category mode!......................................................... 157

Custom template tags ...................................................................................................... <rvn..16S

Simple template tags.............. 168


Inclusion template tag................................................................................... 177
■ Using slugs in URLs ......... 185
• Non-mod el forms........ ......... 207
■ Mo del-bound forms. Custom validators................................................................... ,.^22-b
■ Form field validation . .............................................................. ,^237
• View classes................................................................................................................. ^^240

Basic DjangoORM commands .......... 262

Field filters......................... 268

■ Mixins - eliminating code dup ication...................... 281


■ Pagination....................... 301
■ User registration on the website.................. 319
• User authentication............................ ..^^..335

Page caching . ................................................................. 347


Memory-level caching......................... 348

Request- evel caching.............................................................................. 351

Low-leve! API........................................................................ 353

• Feedback form........ ........................................ ,^.355


■ Fine-tuning the admin panel for the developed website........... .......................... 360

The purposes of the Django


framework
In short, Django framework is used to facilitate server­
side website operations.
In more detail, the interaction process between a user
and a website can be described as follows:
The client initiates a request, for example,
google.com.
The informational packet from the client starts moving
towards the server where the google.com site is
hosted.
On the server (computer), there are specialized
programs, including a web server (usually Apache,
Nginx, etc.).
So, this web server constantly "listens" to incoming
channels. And at the moment a user's request arrives,
the web server needs to redirect the request for
processing to the appropriate website.

Different websites on the server can handle incoming


requests differently, using PHP or CGI scripts, or they
can use frameworks. In this case, Django serves as a
wrapper for the Python language.

In this context, the web server interfaces with WSGI


(Web Server Gateway Interface), a standard for
interaction between a Python program running on the
server-side and the web server itself. The web server
hands over the request processing to this framework.

Next, a specific view within this framework is activated,


which is associated with a particular client request,
resulting in the creation of an HTML page.

This HTML page is first passed back to the web server,


which in turn sends it to the client. The client (user) in
their browser sees a ready-made page that was
generated by the corresponding view within the
framework.

Running a web server at home for


educational purposes.
(Setting up a web server at home for project
development purposes)

How to run the Django framework for home or project


development purposes?
There's good news. All modern computers have an
internal network called "localhost" at 127.0.0.1.
Additionally, the Django framework comes with a built-
in debugging server that can be launched on your
home computer, eliminating the need for an external
server.
Of course, after full development and debugging,
websites are typically hosted on external servers, often
referred to as hosting, where they operate in a "live"
mode.
Now, let's begin the process of setting up your project
on your home computer.
There are several approaches, but we'll start with
creating a new virtual environment.
Django often interacts with various Python packages,
and the version of Python itself matters too. To ensure
consistent and expected results, it's recommended to
use the same versions across your working server
environment, starting from Django and ending with a
specific version of the Python language.
However, your computer might already have certain
packages installed, or you might need to install specific
packages for your project in the future. But this could
negatively impact existing projects and installed
modules on your computer.
To prevent this, you need to isolate the current project
reliably from other projects/environments, including the
global environment.
So, let's create a virtual environment and install the
current versions of Django and the Python language
within it.

Creating a virtual environment.


You will need a Python interpreter for this. If you don't
have it, you should first download it from the official
website www.python.org/downloads/
Navigate to the "Downloads" tab and select the
appropriate operating system.

Proceed to download the latest official release and


install it. I recommend installing it in the "Python"
folder on the "C" drive. Additionally, you'll need to work
with the command line.
Execute

Enter the name of the program, folder, document or


Internet resource that you want to open.

Open: cmd

This task will be created with administrator rights

OK Cancellation Overview...

Next, I'll provide instructions for working with the


Windows operating system. The process is similar on
Linux or macOS.

To open the command prompt, press the Win + R keys


together, then type "cmd" and press Enter. This will
open the command prompt window:
Administrator: CAWindows\system32\cmd.exe — □ X

Microsoft Windows [Version 10.0.14393]


(c) Microsoft Corporation, 2016. All rights reserved.

C:\Users\User>

In this window, you can enter the relevant commands.


To check the functionality of the Python interpreter,
type the following command: python -V

[ :. Administrator: C:\Windows\system32\cmd.exe

Microsoft Windows [Version 10.0.14393]


(c) Microsoft Corporation, 2016. All rights reserved.

C:\Users\Useopython -V
Python 3.10.6

C:\Users\User>

As you can see, the required version of Python has


been installed.

Note that if you are using Linux or macOS, you would


use the command python3 -V (if you have Python 3
installed).

To see the installed packages, you can use the


command pip list.

You might encounter a similar notification in your


console:

This indicates that a new version of pip is available,


and we can update it. To do so, you can use the
command:
python.exe -m pip install --upgrade pip

Ready!
Please note the fact that you are in a global
environment, and all packages currently installed are
also visible in the global space.
Let's move on to the next point, namely to creating our
virtual environment.
First, we need to create a folder where the virtual
environment will be located.
My project will be located at the following path:
C: - > Python -> Django -> travels
You might have a different path. Accordingly, the path
can vary widely.
In the console, if you are on a different drive, switch to
the appropriate drive immediately. For example, if
you've created the project on drive D, switch to that
drive using the command D:
I will navigate directly to the required folder and
provide the following command:
cd C:/Python/Django/travels
Sure, while being in this folder, let's create our virtual
environment using the following command:
Python -m venv venv
Name Change date Type Size

venv Tee 06/09/221146 Folder with files

Name Change date Type Size

Include Tue 06/09/22 11:46 Folder with files

Lib Tue 06/09/22 11:46 Folder with files

Scripts Tue 06/09/22 11:47 Folder with files

Pl pyvenv.cfg Tue 06/09/22 11:46 ’’CFG" fie 1 KB

As you can see, a folder named "venv" has been


created (the name is arbitrary and is specified at
the end of the command Python -m venv venv).
All that's left is to activate the virtual environment
we created. While being in the folder with this
address:
C:/Python/Django/travels
we should execute the following command:
.\venv\Scripts\activate
At the very beginning, don't forget to add a "."
With the prefix at the very beginning "(venv)", we
understand that we are in the created virtual
environment, and everything we do will happen within
the isolated scope of our virtual environment,
separated from the global environment.

If you're working under Linux, you should execute the


following command:
Source venv/bin/activate
If we now execute the command "pip list", we will see
the following:
SB Administratortop: C:\Windows\system32\cmd.exe

C:\>cd D:/Python/Django/travels
The device is not ready.

C:\>cd C:/Python/Django/travels

C:\Python\Django\travels>Python -m venv venv

C:\Python\Dj ango\travels >.\venv\Scripts\activate

(venv) C:\Python\Django\travels>pip list


Package Version

pip 22.2.1
setuptools 63.2.0

[ ] A new release of pip available: -> 22.2.2


[ ] To update, run: python.exe -m pip install --upgrade pip

(venv) C:\Python\Django\travels>python.exe -m pip install --upgrade pip


Requirement already satisfied: pip in c:\python\django\travels\venv\lib\site-packages (22.2.1)
Collecting pip
Using cached pip-22.2.2-py3-none-any.whl (2.0 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.2.1
Uninstalling pip-22.2.1:
Successfully uninstalled pip-22.2.1
Successfully installed pip-22.2.2

(venv) C:\Python\Django\travels>________________________________________________________________
If you had modules installed in the global environment,
you won't see them here. Additionally, you should
perform the update again, as we did earlier.

To exit the virtual environment, you need to enter the


command "deactivate".

Integrated Development Environment


(IDE) PyCharm
(For working with Python code)
Understood. Since you use the PyCharm integrated
development environment for your work, you will
perform all further actions with the virtual environment
within this environment.

Admintop: C:\Windows\system32\cmd.exe

The device is not ready.

C:\>cd C:/Python/Django/travels

C:\Python\Django\travels>Python -m venv venv

C:\Python\Django\travels>.\venv\Scripts\activate

(venv) C:\Python\Django\travels>pip list


Package Version

pip 22.2.1
setuptools 63.2.0

] A new release of pip available: -> 22.2.2


[ ] To update, run: python.exe i pip install upgrade pip

(venv) C:\Python\Django\travels>python.exe -m pip install --upgrade pip


Requirement already satisfied: pip in c:\python\django\travels\venv\lib\site-packages (22.2.1)
Collecting pip
Using cached pip-22.2.2-py3-none-any.whl (2.0 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.2.1
Uninstalling pip-22.2.1:
Successfully uninstalled pip-22.2.1
Successfully installed pip-22.2.2

(venv) C:\Python\Django\travelsydeactivate
C:\Python\Django\travels>_________________________________________________________________________

As we can see, the (venv) prefix is no longer present.


Since you use the PyCharm integrated development
environment for your work, you will perform all further
actions with the virtual environment within it.

To download this IDE, you can follow the link:


https://www.jetbrains.com/ru-ru/pycharm/
In the beginning, using the free version of PyCharm
Community should suffice. Now, let's open the created
project. Follow these steps:

Go to the "File" menu.


In the dropdown menu, select "Open."
Please note that the exact steps and user interface may
vary based on the version of PyCharm you are using.
In the next window, select the "travels" folder
□ Open File or Project

W 5 *3 X Q <g Hide path

C:\Python\Django\travels ▼
■ Intel
> taKMSAutoS
> >7 NewBot
> ► parser_avito
> M Program Files
> ta Program Files (x86)
v M Python
v M Django
v ► travels
> M idea
> M venv
> M Python-3.S.3
> M Python371
> M Users
> M Webhook
> Windows
> MRwneoRnorwHr

? OK Cancel
And click "OK".
After this action, the project will be loaded.

File Edit View Navigate Code Refactor Run Tools VCS Window Help

travels M venv

t; [■] Project ▼ "r 0 —


o'
v to travels C:\Python\Django\travels
* V l-k +
v ■ venv library root
M Include
> MLib
> ta Scripts
pyvenv.cfg
> Hill External Libraries

Scratches and Consoles

If you go to the terminal at the bottom part:

P Version Control := TODO O Problems H Terminal $ Python Packages *•* Python Console
ID Indexing completed in 52 sec. Shared indexes were applied to 29% of files (1,271 of 4,289). (18 minutes ago)

We will see the activated virtual environment.


If, for some reason, this didn't happen... for example:
Terminal: Local x + sz

Windows PowerShell
(C) 2016 Microsoft Corporation. All rights reserved.

PS C:\Python\Django\travels>

it's necessary to manually activate the environment by


following these steps:
Open the "File" menu.
Select "Settings..."
In the opened window, go to "Project:travels" and
choose "Python interpreter."
Then, click on the arrow on the right and choose the
desired interpreter from the list.
Please note that the exact steps and user interface
might vary based on the version of PyCharm you are
using.
QU Quite often, when opening the terminal, an error
appears:

.\venv\Scripts\activate : Hgbosmomho 3arpy3UTb


pain C:\path\venv\Scripts\activate.ps1, maK KaK
BbinonHeHue ^eHapueB oTKn^neHo b ^Tol
cucmeMe.
finn nonyneHun gononHumenbH^x CBegeHui cm.
about_Execution_Policies no agpecy
http://go.microsoft.com/fwlink/?LinkID=135170.
cmpoKa:1 3HaK:1
.\venv\Scripts\activate

Categoryinfo : 0i±iu6ka 6e3onacHOcmu: (:) [],


PSSecurityException
FullyQualifiedErrorid : UnauthorizedAccess

The solution involves the following steps:

1. Open the PowerShell terminal as an administrator.


Click the Start button, type "PowerShell," right­
click on "Windows PowerShell," and select "Run
as administrator."

2. Paste and execute the command: Set­


ExecutionPolicy RemoteSigned
3. When prompted, respond with -A
2 Administrator: Windows PowerShell — □ X
Windows PowerShell
(C> 2016 Microsoft Corporation. All rights reserved.

PS C:\Users\User> Set-ExecutionPolicy RemoteSigned


I Changing the Execuion Policy
The execution policy protects the computer from untrusted scripts. Changing the execution policy can compromise
system security, as described in the help topic called by the about_Execution_Policies command. Do you want to

[Yl Ves Y aH - A rN1 No - N [L] No to all L [S] Suspend s [?] Help -


PS C:\Users\User>

You can read about the impact of disabling execution


policies in PowerShell on security through the
following link:
https://docs.microsoft.com/ru-
ru/powershell/module/microsoft.powershell.core/
about/about execution policies?
view=powershell-7.2

If you've followed all the steps correctly, after the


manipulations, your terminal should look like the
screenshot below:
S file Edit View Navigate Code Befactor Run lools VCS Window Help travels - Administrator

M travels
Project

H Project ▼ -3 H "r Cl —

v travels C:\Python\Django\travels
V

v M venv
M Include
* M Lib

> Bi Scripts
Lo.gitignore
t3 pyvenv.cfg
* Hill External Libraries

Scratches and Consoles

Search Everywhere Double Shift

Goto File Ctrl+Shift+N

Recent Files Ctrl+E

Navigation Bar Alt+Home

Drop files here to open them

Terminal: Local X + v

Windows PowerShell
(C) 2016 Microsoft Corporation. All rights reserved.
Cvenv) PS C:\Python\Django\travels> Q

Installing the Django framework.


At this stage, let's proceed with installing Django
while being in the project's root folder. To do this, in the
PyCharm terminal, you should enter the command:
pip install django

If you enter the command pip list, you will see that the
Django package has been added to our list.

To view the list of core commands of the Django


package, simply type the command django-admin
[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
optimizemigration
runserver
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver
ilote that only Django core commands are li!
the environment variable DJANGO_SETTINGSJ'
tvenv) PS C:\Python\Django\travels> [
Creating our project.
To create our project, type the following command in
the terminal command line:
Django-admin startproject travels
After running this command, we discover a new folder
within it...

.idea Cp 07.09.22 13:52 File folder

travels Thu 08.09.22 11:54 File folder

venv Cp 07.09.22 13:42 File folder

Name Date of change Type Size

travels Thu 08.0922 11:54 File folder

fl manage, py 1 KB
Thu 08.09.22 11:54 JetBrains PyCharm ...

Name □ate modified Type Size

9 _init_.py Thu 08.0922 11:54 JetBrains PyCharm ... 0 Kb


® asgi.py Thu 08.0922 11:54 JetBrains PyCharm ... 1 KB

9 settings.py Thu 08.09.22 11:54 JetBrains PyCharm ... 4 KB

9 urls.py Thu 08.0922 11:54 JetBrains PyCharm ... 1 KB

9 wsgi.py Thu 08.0922 11:54 JetBrains PyCharm... 1 KB

The name of the website usually coincides with the


domain name where the site will be hosted. We intend
to name the site "travels."
In the screenshot, it's evident that two "travels"
folders were created. Inside the nested "travels"
folder, there's another folder containing files that define
the current project configuration. This folder is also
referred to as the configuration package. The purpose
of the files in this package will be explained later.
We also see the "manage.py" file. This file is used to
manage the current website. For instance, we'll create
applications, perform database migrations, run a test
web server, and so on through this file.
Everything is ready to set up a test web server!
To do this, we will navigate to the "travels" folder using
the command:
cd travels
Start our server using the command:
python manage.py runserver
(venv) PS C:\Python\Django\travels> cd travels
(venv) PS C:\Python\Django\travels\travels> python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
September 08, 2022 - 12:13:30
Django version 4.1.1, using settings 'travels.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

The main page of our website will be accessible at the


following address: http://127.0.0.1:8000/
Clicking on this link will open a browser, and the
following page will appear:

django View release notes for Django 4.1

M
The install worked successfully! Congratulations!
You are seeing this page because DEBUG=True is in your
settings file and you have not configured any URLs.

q Django Documentation <> Tutorial: A Polling App Django Community


Topics, references, & how-to's Get started with Django Connect, get help, or contribute

The advantage of the debugging web server is that it


automatically reloads when we make changes to our
project's code. Unfortunately, this doesn't always work.
So, if you find that nothing happens after making code
changes, simply restart the web server. You can do this
by pressing the CTRL-BREAK combination.
Please note that when we initially launched this
emulator of a real web server, a file named
"db.sqlite3" appeared. This is an SQLite3 database
file. By default, Django uses this type of database. In
the future, we can switch to any other database
management system supported by this framework,
such as PostgreSQL, MariaDB, MySQL, Oracle, and
SQLite.
If for any reason you decide to run the test server on a
different port, you can do so by adding the desired port
number to the command string.
Python manage.py runserver 4000

(venv) PS C:\Python\Django\travels\travels> python manage.py runserver 4000


Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate1 to apply them.
September 08, 2022 - 12:32:07
Django version 4.1.1, using settings 'travels.settings'
Starting development server at http:7/127.0.0.1:4000/

As you can see, our server has reloaded on the new


port.
With that, we've completed all the preparatory steps
and launched the test web server.
Model-View-Template (MVT)
According to Django's philosophy, we should create a
new application within the scope of our website.
Django developers have decided that each part of the
site should be represented as a separate application.
For instance, when creating a blog, we would define an
application for creating the blog's pages. Later, if we
need a user comment feature, we create a new
application for this functionally independent part. The
same applies to features like creating a survey, which
would also be separated into its own application, and so
on.
Every logically and functionally independent part of the
site implies the creation of a separate application.
Django applications should be designed to be as
independent as possible, so that when creating a new
website, you can simply copy the code and move it to
the new project. At the very least, this is what we
should strive for.
So, let's create the first application within our site to
handle the basic functionality. To begin, let's navigate to
the directory named "travels" located inside the
"travels" directory. To do this, use the following
command in the terminal:
cd travels
And once inside this directory, execute the command:
python manage.py startapp traveler
traveler - the name of our application.
As you can see, a new directory named "traveler" has
appeared, containing certain files.
Firstly, we see the file named "init.py". It indicates that
this "traveler" directory is nothing but a package.
Additionally, there's a "migrations" folder. It's meant
for storing database migrations.
"admin.py" is used to connect our application with the
site's admin panel.
"apps.py" involves configuration and setup for the
current application.
"models.py" is where ORM models are stored.
"tests.py" is a module for testing procedures.
"views.py" stores the views or controllers of the
current application.
Next, we need to register our application in the project
of our site so that Django is aware of its existence and
can work with it correctly. To do this, open the "travels"
folder, find and open the "settings.py" file. In this file,
locate the "INSTALLED_APPS" list and add the name
of our application to the end of the list, as shown in the
screenshot below.

# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'traveler'
]
Here's an improved version:
If you open the "apps.py" file:

We will see the following code:

from django.apps import AppConfig


class TravelerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'traveler'

Returning to the "settings.py" file and making a slight


modification to the entry:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'traveler.apps.TravelerCon fig'

At this stage, the application is created and registered.


Next, let's create a handler for the main page of the
website. To do this, we need to define the view for this
page.

Presentation Views
(Creation of the main page
handler)
In Django, views can be implemented either as
functions or as classes. Let's start by using a function,
as it's the simplest implementation to understand. We
define all views in the views.py file.

Let's define the first view function for the main page.
def index(request):
return HttpResponse("Application page traveler.")
The function name "index" is chosen arbitrarily, but
typically, for the main page, it's customary to use this
name.
Next, (request) essentially refers to an HttpRequest
object that contains information about the request,
session, cookies, etc. So, using the request variable, we
have access to all the information within the current
request context.
As an output, this function should create an instance of
the HttpResponse class.
The content of the main page will be the string
("Application page content.").
However, in order to use this, you need to import the
HttpResponse class.

from django.http import HttpResponse


from django.shortcuts import render

def index(request):
return HttpResponse("Application page traveler.")

In the simplest case, the view function is created.


Now, you need to associate this function with the
appropriate URL address.
To do this, in the "travels" configuration package, you
should open the "urls.py" file and add to the list

urlpatterns = [
pathCadmin/', admin.site.urls),
]

we add another route.


As we can see, there's already one route in it. This is
the access to the admin panel of our site.
In a similar manner, we add another route.

urlpatterns = [
pathCadmin/', admin.site.urls),
path('traveler/', index),
]

We insert the path function, define the pattern


'traveler/', and then provide a link to the view
function that will be triggered upon the request to
'traveler/'.
We also need to import the index function.

from django.contrib import admin


from django.urls import path
from traveler.views import index

urlpatterns = [
pathCadmin/', admin.site.urls),
path('traveler/', index),
]
H Project ▼ © J. -r Ct — ft settings.py x ft views.py < I ft urls.py ft apps.py

Project ' to travels C:\Python\Django\travels

v to travels □ from django.contrib import admin


- El traveler f®om django.urls import path
> to migrations
k (dfrom traveler .views import index
ft _init_.py
ft admin.py
ft appspy
Hurlpatterns = [
ft models.py path('admin/1, admin.site.urls),
ft tests.py path('traveler/', index),
ft views.py ]
- to travels
ft_init_.py
ft asgi.py

settings.py
ft urls.py
ft wsgi.py
tj db.sqlite3
ft manage.py

> to venv
> Hill External Libraries

Scratches and Consoles

The question arises: what will be the page where the


index view function will be triggered?
In reality, it will look like this:
http://127.0.0.1:8000/traveler/
That is, the prefix traveler/ will be added to the
domain of our site.
The route
http://127.0.0.1:8000/traveler/
will activate the index function.
If an error occurs, you need to mark our traveler
directory as the main directory. To do this, click on the
directory with the right mouse button, then click on
"Mark Directory as" and select "Sources Root". After
that, the error should disappear.
After doing this, we will test the functionality of our
page by entering the following URL in the browser:
http://127.0.0.1:8000/traveler/
But before that, you need to start our web server using
the command python manage.py runserver.
However, first navigate to the travels directory using
the cd travels command.

Cvenv) PS C:\Python\Django\travels> cd travels


(venv) PS C:\Python\Django\travels\travels> python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migrationCs). Your project may not work properly until you apply the migrations for appts): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
September 12, 2022 - 12:45:23
Django version 4.1.1, using settings 'travels.settings'
Starting development server at http://127.B.B.l:8BBB/
Quit the server with CTRL-BREAK.

We navigate to the page


http://127.0.0.1:8000/traveler/

<- -» C (D 127.0.0.1:8000/tr a veler/

Traveler app page-


Everything is working!
Using the same approach, we can define as many view
functions and URL routes as needed.
Also, please note that if you enter the address in the
browser's URL bar
http://127.0.0.1:8000/
then you will see a page like this
<r -> C ® 127.0.0.1:8000 '

Page not found (404>

Request Method: GET


Request URL: http://127.0.0.1:8000/

Using the URLconf defined in travels.urls, Django tried these URL patterns, in this order:
1. admin/
2. traveler/

The empty path didn’t match any of these.

You're seeing this error because you have debug = True in your Django settings file. Change that to False, and Django will display a standard 404 page

In order to get everything working again, you need to


change the line in the urls.py file to

urlpatterns = [
path('admin/', admin.site.urls),
pathC', index),
]

So, instead of path('traveler/', index), insert a blank


line.
Refresh the browser page, and you will see the familiar
page.
However, this approach, where the application routes
are directly defined in the project's configuration file,
violates the principle of application independence.
If you ever want to move this application to another
project, you would need to copy these routes as well.
This is not very convenient.
How can this be resolved?
Django allows you to pass a list of URL addresses for
the application and their associated view functions as
the second parameter, instead of just a view function.
This is done using the include function, which we
import, and instead of all the routes we defined, we just
write one line..

from django.contrib import admin


from django.urls import path, include
from traveler.views import index

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

travels travels ) travels > urls.py

■c B Project ▼ © Z -? — settings.py Up views.py r^> travels\urls.py traveler\urls.py


'o'
v M travels
v ^traveler from django.ccntrib import admin
> El migrations from django.urls import path, Include
& _Jnit_.py from traveler.views import index
E^i admin.py
& apps py
urlpatterns = [
models.py
pathC1 admin/', admin.site.urls),
r1". tests.py
pathC traveler/1, include[traveler.urls)),
& urls.py
views.py ]
- El travels
& _init_.py
& asgi.py
settings.py
6 urls.py

wsgi.py
db.sqlite3

A certain prefix is still specified, in our case traveler/,


and then the include function is called. We pass the
path to the file that will contain the routes of our
application to this function. Currently, there is no
urls.py file in the traveler application. So, we'll create
one.
The contents of this file will be as follows:.
from django.urls import path

from .views import *

urlpatterns = [
pathC', index)
]

First, we import the path function to create our routes.


Then, we import all our view functions using from
.views import *. We define the standard urlpatterns
collection. Within this list, we define all the routes for
the current application.

The index view handler will correspond to the following


route:

http://127.0.0.1:8000/traveler
The prefix traveler will be attached to the domain
because we specified it in the urls.py file of the overall
project configuration.
To do this, we use the path('travels/',
include(traveler.urls)) line, which is located in the
travels directory.
This file should be created in the traveler application
directory.
Copy and paste the route
http://127.0.0.1:8000/traveler
into the browser. You will see the familiar page
<- -> C O 127.0.0.1:8000/traveler/

Traveler app page.


In this simple way, you can create view functions and
define routes.

Continuing with the topic of routing, let's make a few


adjustments. Open the urls.py file in the travels
configuration package. In the urlpatterns list, remove
the traveler/ prefix, which is associated with the route
of our traveler.urls application, and leave it as an
empty string. Now, all routes related to this application
will be based directly on the domain name.
from django.contrib import admin
from django.urls import path, include
from traveler.views import index

urlpatterns = [
path('admin/', admin.site.urls),
pathC', include('traveler.urls')),
]

« H Project ▼ © Z -? 1> — ft settings.py ft views.py ft travels\urls.py ft travele


o
v fc travels >ources root, C:\Python\Django\travels
v M travels Elfrom django.contrib import admin
£■ traveler
from django.urls import path, include
> Bi migrations
Hfrom traveler.views import index
iv _Jnit_.py
ft admin.py
ftapps py
-urlpatterns = [
ft models.py path(1 admin/’, admin.site.urls),
ft tests.py 8 " pathC’, includeCtraveler .urls’)),
ft urls.py ]
ft views.py

v Bi travels
ft _init_.py
ft asgi.py
ft settings.py
ft urls.py
ft wsgi.py

For example, let's open the urls.py file of our


application

from django.urls import path


from .views import *
urlpatterns = [
path(", index),
]

In it, path('', index) will correspond to the main page


http://127.0.0.1:8000/
For clarity, let's define one more route.
This is done in a similar manner.

from django.urls import path


from .views import *
urlpatterns = [
path(", index),
pathCcats/', categories),
]
Also, we specify the view function that will handle this
route and render the page. We also need to import it. To
avoid listing the functions to import every time with
commas, we use *.
The template path('cats/', categories) will
correspond to the address
http://127.0.0.1:8000/cats/
But that's not enough, as we remember. Next, we open
the views.py file and add the view function.
def index(request):
return HttpResponse("Application page
traveler.")

def categories(request):
return HttpResponse("Articles by categories.'")
tj [■} Project ▼ © I <1 " settings.py views.py tests.py x travels\urls.py :< traveler\urls.py
a- v ft travels sources root Ct\Python\Django\travels 1 1 import ...
v M travels ■ X
v Bi traveler 1 4 ddef
index(request):
> Eft migrations 1 5 A return HttpResponse( ‘Traveler app page.11)
fl, _init_.py
admin.py
7 A def categories(request):
S apps.py
a A return HttpResponse("Articles by Cateoorv.1')
models py
tests.py 9
$ urls.py
views, py
v Bi travels
ft irit py
fi. asgi.py
[£■ settings.py
ft Nrlspy
giwsgi.py

We launch the web server and navigate to the page


http://127.0.0.1:8000/cats/

<- -> C Q) 127.0.0.1:8000/cats/

Articles by category

As we can see, everything is working.


Let's continue our example with routing.
We will display our categories based on their index.
For example,
http://127.0.0.1:8000/cats/1/
http://127.0.0.1:8000/cats/2/ and so on.
A
Currently, we don't have such a route. Let's fix that.
To do this, in the part where our category is being
generated, we will add a numerical parameter.

from django.urls import path


from .views import *
urlpatterns = [
path(", index),
path('cats/<int: orderJd/>', categories),
]

We enclose the parameter in angle brackets <>,


specifying the data type as an integer, i.e., int. Next, we
give a name to this parameter, let's call it order_id. In
this case, the categories function will be associated
with a route that has the prefix 'cats/...' and is
connected to a number, for example,
http://127.0.0.1:8000/cats/1/.
Now, how do we capture this numerical parameter in
the categories function!

In the views.py file of the "traveler" application, we


will locate the categories function and specify this
parameter. Let's also display this order_id number on
the page.

def index(request):
return HttpResponse('"Application page traveler.")

def categories(request, order_id):


return HttpResponse(f"<h1> Articles by categories.
</h1><p>{order_id}</p>")

travels sources root, C:\Python\Django\travels □ from django.urls import path


travels
v El traveler □from .views import *
> El migrations
g_init_.py
Hurlpattems = [
admin.py
path (’ ’, index),
gappspy
? path('cats/<int:order_id>/’, categories),
fa rrodels.py
g testspy ]
g urls.py
g views, py
v I travels
g_init_.py
g asgi.py
g settings.py
g urls.py
______ fiwsiipy_________________________

Let's restart the server.


Enter the route in the browser's address bar, for
example, http://127.0.0.1:8000/cats/2/
And we will see the displayed page.

<- -> C? © 127.0.0.1:8000/cats/2/

Articles by category
2

zh
If you enter
http://127.0.0.1:8000/cats/
without a number, you will see a 404 page
<- -> O <X> 127.0.0.1:8000/cats/ <

Page not found (404>


Request Method: GET
Request URL: http://127.0.0.1:8000/cats/

Using the URLconf defined in travels.uris, Django tried these URL patterns, in this order:
1. admin/
2.
3. cats/<int:order_id>/

The current path, cats/, didn’t match any of these.

You’re seeing this error because you have debug = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

Or if you enter http://127.0.0.1:8000/cats/ek/


, i.e., a non-numeric value, you will also get a 404
error, as the correspondence won't be met.

In addition to the int type, Django supports other types


that we can specify here:

str - any non-empty string, excluding the '/'


character;
int - any positive integer, including '0';
slug - can use any Latin letters, including hyphens and
underscores;
uuid - digits, lowercase Latin letters, hyphens;
path - any non-empty string, including the '/'
character.
As an example, let's replace int with slug and see how it
affects our template.

from django.urls import path

from .views import *

urlpatterns = [
path(", index),
pathCcats/<slug:order_id>/', categories),
]
Let's go to the browser and see how it works

<- -» 0 © 127.0.0.1:8000/cats/l/

Articles by category
1

Yes, if you append a letter to the number, it will still work.


This is because we are using the slug type, which allows
for a combination of letters and numbers.

«- -> 0 © 127.0.0.1:8000/cats/i a/

Articles by category
ia

Exactly, Cyrillic letters will not work in this case


because they are considered invalid characters within
the slug type in Django's URL routing system. Only
Latin letters, numbers, hyphens, and underscores are
allowed in slug patterns.
<r C G> 127.0.0.1:8000/cats/1bi Q.

Page not found (404)

Request Method: GET


Request URL: http://127.0.0.1:8000/cats/1%D1%8B

Using the URLconf defined in travels.urls, Django tried these URL patterns, in this order:
1. admin/
2.
3. cats/<slug:order_id>/

The current path, cats/lbi, didn’t match any of these.

You're seeing this error because you have debug = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

Indeed, if you ask the reasonable question of why we


need this! After all, numbers are simpler. The thing is
that words are better indexed by search engines and
better understood by users.

Having demonstrated how it works as a demonstration,


I'll revert to how it was.
Working with regular expressions.
The function re_path()
If for some reason the above-mentioned patterns are
not sufficient, in Django, you can use a special function
called re_path(). It allows you to do the same thing but
with the use of regular expressions. For example, you
can define a URL where you can specify the year as
four digits. Then you can describe the route using this
function as follows.
from django.urls import path, re_path

from .views import *

urlpatterns = [
path(", index),
path('cats/<int:order_id>/', categories),
re_path(r'~archive/(?P<year>[0-9]{4})/',
archive),
]

We import this function and add one line in which we


placed the prefix and the archive handler function,
specifying in the regular expression that the year
should consist of four digits
I travels travels :■ traveler rjurls.py

Fl Project O -£■ C® — settings.py views,py tests,py i5> travels\urls,py [£> traveler\urls.py models.py [£> apps.py

travels sources root, C:\Python\Django\travels Pfrom django.urls import path, re_path


v to travels
v El traveler lifrom .views import *
> El migrations
ft_init_.py
Jurlpatterns = [
[5* admin.py
path('1, index),
e5> apps.py
pathU cats/<int:order_id>/', categories),
[5> models.py
& tests.py re_path(r'Aarchive/(?P<year>[G-9]{4})/', archive),
i5.urls.py ]
i£. views.py
EB travels
ft_init_.py
asgi.py
l5> settings, py
$ urls.py
[5> wsgi.py

Next, let's go to the views.py file and accordingly


define this function.

from django.http import HttpResponse


from django.shortcuts import render

def index(request):
return HttpResponseC'The page of the application
traveler.")

def categories(request, order_id):


return HttpResponse(f'<h1> Articles by categories.
</h1><p>{order_id}</p>")

def archive (request, year):


return HttpResponse(f"<h1> Yearly archive</h1>
<p>{year}</p>")

This function takes two parameters, one of which is


year.
Let's check how this will work. Go to the browser and
enter the following URL string
http://127.0.0.1:8000/archive/2022/
‘--with Q 127.0.0.1:8000/archive/2022/

Archive by years
2022

It's working fine!


If we specify 3 or 2 digits, we will encounter an error
because the pattern does not match. As we remember,
we should have 4 digits.
Processing GET and POST requests

The structure of a URL can contain the following


parameters:
http://127.0.0.1:8000/?
name=Akademgorodok&order id=smartphon
? - a special character.
name=Akademgorodok - key-value pair.
& - delimiter character.
order_id=smartphon - key-value pair.
The entire string ?
name=Akademgorodok&order_id= smartphon is an
example of a GET request.
To extract a key from a GET request, you use the first
(request) parameter in the view function.
def categories(request, order_id)
Through it, we can access the special request.GET
dictionary where all this data is stored.
Let's look at a specific example of how GET requests
work and how to catch them.
Open the views.py file and add two lines to the
categories function.

def categories(request, order_id):


if(request.GET):
print(request.GET)
return HttpResponse(f"<h1> Articles by Categories.
</h1><p>{order_id}</p>")
Before printing the dictionary to the console, let's check
the condition to see if this dictionary exists in the
string. And if it is present there, then we will output it to
the console of our terminal. To do this, let's put the
following line in the browser
http://127.0.0.1:8000/?
name=Akademgorodok&order id=smartphon

<- -> C Q 127.0.0.1:8000/?name=Akademgorodok&order_id=smartphon

Application Page traveler.

The page has loaded. Let's access our terminal now.

Django version 4.1.1, using settings 'travels.settings'


Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[18/Sep/2022 16:48:43] "GET/?
name=Akademgorodok&order_id=smartphon
HTTP/1.1" 200 47
If we remove the GET request from our browser and
record http://127.0.0.1:8000/cats/1/

O 127.0.0.1:8000/cats/1/

Articles by category

We will receive
[18/Sep/2022 16:52:23] "GET/ HTTP/1.1" 200 47
[18/Sep/2022 16:53:58] "GET/cats/2/ HTTP/1.1" 200 56
[18/Sep/2022 16:54:02] "GET/cats/1/ HTTP/1.1" 200 56

As we can see, there is no dictionary anymore.


S Project ▼ © I -r * - ft settings.py ft views.py ft tests.py X ft travels\urls.py ft traveler\urls.py ft models.py ft apps.py
v ta travels sources root, C::\Python\Dj an go\travels 1 fflimport ...
v M travels
v traveler 4 udef index(request):
> tl migrations
return HttpResponse("Travelerapppage.")
ftinit.py
ft admin,py
7 Rdef categories(request, order_id):
ftappspy
ft models.py if(request.GET):
ft tests.py print(request.GET)
ft urls.py 10 f return HttpResponse(f "<hl>Articles by category. </hl><p>{order_id}</p>") ^p>")
ft views.py
v travels 12 Rdef archive (request, year):
ft_init_.py return HttpResponse(f "<hl>Arive by yearm</hlxp>-{year}</p>")
ft asgi.py
ft settings.py
ft urls.py
ft wsgi.py categoriesf)

Terminal: Local

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
September 18, 2022 - 16:45:52
Django version 4.1.1, using settings 'travels.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[18/Sep/2022 16:48:43] "GET /?name=Akademgorodok&order_id=smartphon HTTP/1.1" 200 47
[18/Sep/2022 16:52:23] "GET / HTTP/1.1" 200 47
[18/Sep/2022 16:53:58] "GET /cats/2/ HTTP/1.1" 200 56
[18/Sep/2022 16:54:02] "GET /cats/1/ HTTP/1.1" 200 56

You can work in a similar way with a POST request.

def categories(request, order_id):


if(request.POST):
print(request.POST)
return HttpResponse(f"<h1> Articles by categories.
</h1><p>{order_id}</p>")

Usually, a POST request works in conjunction with


forms when we use it to submit data such as a
username and password.
Exception handling when making
requests to a server.

— If you enter a string with a non-existent address, for


example, http://127.0.0.1:8000/cats/hjhsdjhsdj
We will receive a page 404
C 0 127.0.0.1:8000/cats/hjhsdjhsdj

Page not found (404)


Request Method: GET
Request URL: http://127.0.0.1:8000/cats/hjhsdjhsdj

Using the URLconf defined in travels.uris, Django tried these URL patterns, in this order:
1. admin/

3. cats/<int:order_id>/
4. Aarchive/(?P<year>[0-9]{4})/

The current path, cats/hjhsdjhsdj, didn’t match any of these.

You’re seeing this error because you have debug = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

We see such a page exclusively during debugging, i.e.,


when the value is
DEBUG = True
You can change this value in the "settings.py" file
in the "travels" folder.
In non-debug mode, i.e., when DEBUG = False (in
production mode), you will receive the following
message in the console.

Not Found: /favicon.ico


[20/Sep/2022 11:48:23] "GET /favicon.ico HTTP/l.l" 404 2612
C:\Python\Django\travels\travels\travels\settings.py changed, reloading.
CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False,
(venv) PS C:\Python\Django\travels\travels> F

This means that you don't have any allowed hosts


specified. To fix this, you can specify the hostname in
the same file by adding it to the line
ALLOWED_HOSTS = [‘http://127.0.0.1’]
And then, restart the server as well as our webpage. As
a result, you will see...

<- -> O Q 127.0.0.1:8000/cats/hjhsdjhsdj

Bad Request (400)


Let's make a more user-friendly page in this case. In
this situation, a 400 error means that the server
couldn't understand the request.
To do this, open the "urls.py" file in the same
configuration package where the "settings.py" file is
located. In this file, you can define a special handler for
this page.
Add the following line at the very end: handler400 =
pageNotFound
And don't forget to import the necessary function.

from traveler.views import pageNotFound


from django.contrib import admin
from django.urls import path, include
from traveler.views import index
from traveler.views import pageNotFound
urlpatterns = [
path('admin/', admin.site.urls),
path(", include('traveler.urls')),
]
handler400 = pageNotFound

Currently, we don't have the pageNotFound function.


So, let's define it in the "views.py" file within the
"traveler" package.

def archive (request, year):


return HttpResponse(f"<h1> Archive by years </h1>
<p>{year}</p>")
def pageNotFound(request, exception):
return HttpResponseNotFound(,<h1> Page not
found </h1>')
To use the HttpResponseNotFound class, you need to
import it

from django.http import HttpResponse,


HttpResponseNotFound
from django.shortcuts import render

def index(request):
return HttpResponse("Application page traveler.")

def categories(request, order_id):


return HttpResponse(f"<h1> Articles by categories.
</h1><p>{order_id}</p>")

def archive (request, year):


return HttpResponse(f"<h1> Yearly archive</h1><p>
{year}</p>")

def pageNotFound(request, exception):


return HttpResponseNotFound('<h1> Page not
found</h1>')
You have obtained a more user-friendly page now.

<- -> O (P 127.0.0.1 8000/gffg

Page not found

In a similar manner, you can override handlers for other


exceptions as well.
For example:
Handler500 - server error
Handler403 - access denied
All of these handlers come into play when DEBUG =
False is set.
You can find more detailed information about exception
handling by following this link: Django Exceptions
Documentation.

In the future, if you can't access these links due to


obsolescence or any other reason, it's always advisable
to search for documentation on the official Django
website using their search feature.
Creating 301 and 302 redirects.

301 - The page has been moved to another permanent


URL
302 - The page has been moved to another temporary
URL

Such redirects are often used in website development.


For this purpose, the django.shortcuts.redirect
function is used.
Open the views.py file of our project and add the
following line.

from django.http import HttpResponse


from django.shortcuts import render, redirect

def index(request):
return HttpResponse("Application page traveler.")

def categories(request, order_id):


return HttpResponse(f"<h1> Articles by categories.</h1>
<p>{order_id}</p>")

def archive (request, year):


if int(year) > 2022:
return redirect('/')
return HttpResponse(f"<h1>Yearly archive </h1><p>
{year}</p>")

to travels sources root C:\Python\Django\travels i'.. from django.http import HttpResponse


v to travels □ from django.shortcuts import render, redirect
to traveler
> to migrations
i def index(request):
A -init—py
m return HttpResponse("Travelerapp page.")
admin.py
A appspy

r^. models.py
def categoriesCrequest, order_id):

tests.py
F return HttpResponse (f "<hl>Articles by category </hlxp> {or de r_id}</p>")
A urls.py

yiews.py i def archive (request, year):


v to travels if int(year) > 2022:
ft_mit_.py
return redirect(/')
ft asgipy
tJ return HttpResponse(f"<hl>Archivebyyear</hl><p>{year}</p>")
ft settings.py
ft urls.py
ft wsgi.py

db.sqliteB
ft manage,py 17
> to venv

If the year in the browser's address bar is greater than


2022, it will be redirected to the main page. To
accomplish this, we will restart the server using the
command we already know:
python manage.py runserver
When you refresh the browser page, you will see a page
like this.

(D 127.0.0.1:8000

traveler app page

In the console, you will also see that a redirect with a


status code of 302 was triggered
Terminal: Local x + v

[22/Sep/2022 12:06:01] "GET /archive/2032 HTTP/1.1" 301 0


[22/Sep/2022 12:06:01] "GET /archive/2032/ HTTP/1.1" 302 0
[22/Sep/2022 12:06:01] "GET / HTTP/1.1" 200 W

Not Found: /favicon.ico


[22/Sep/2022 12:06:03] "GET /favicon.ico HTTP/1.1" 404 2612

As we remember, this is a temporary redirect.


To make it a permanent redirect, we will change the line
to:

def archive (request, year):


if int(year) > 2022:
return redirect('/', permanent=True)
return HttpResponse(f"<h1>Yearly archive </h1><p>
{year}</p>")

We return to the browser and refresh the page


Terminal: Local + v

Quit the server with CTRL-BREAK.


[22/Sep/2022 12:12:50] "GET / HTTP/1.1" 200 47
[22/Sep/2022 12:13:23] "GET /archive/2012/ HTTP/1.1" 200 46
[22/Sep/2022 12:13:30] "GET /archive/2030/ HTTP/1.1" 301 0
[22/Sep/2022 12:13:30] "GET / HTTP/1.1" 200 47

And we find in the console that a 301 redirect has been


triggered.

It's important to mention one more thing. Explicitly


hardcoding the URL we are redirecting to is considered
bad practice. If the destination page's address changes
for any reason, we would have to manually update the
URL everywhere the function is used, which can be
inconvenient and error-prone. It's not recommended to
do it this way.

To avoid this practice, we specify the name of the URL


in the redirect function instead of its explicit address.
For example, for the main page, let's define the name
as 'home'. Next, we need to make these changes in the
URL route. Open the 'urls.py' file of your application,
find the route for the main page, and add the following
line to it

urlpatterns = [
path('', index, name='home'),
path('cats/<int:order_id>/', categories),
re_path(r'~archive/(?P<year>[0-9]{4})/', archive),
]
travels sources root C:\Python\Django\travels import ...
v travels
ra traveler urtpatterns = [
> ra migrations path(''( index, name=1 home'),
f5._init_.py
path('cats/<int:order_id>/', categories),
admir.py
re_path(r1Aarchive/(?P<year>[0-9]{4})/ ', archive),
fiapps.py
9 ]
E^ models, py
S tests, py
44 urls.py
E^views.py

ra travels
6_init_.py
ftasgi.py
ijsettings.py
Suri spy
wsgi.py
db.sqliteS
manage.py

If you refresh the page and enter


http://127.0.0.1:8000/archive/2045/
, you will be redirected to the main page.
Try entering a year that you haven't entered before to
ensure that the browser doesn't use cached data.
<- -> O (D 127.0.0.1:8000

traveler app page


Data Models. Migrations.
Data models are responsible for storing and
manipulating a website's data. Standard database
management systems are often used for this purpose.
These include SQLite, MySQL, PostgreSQL, Oracle,
and others.
For simplicity and clarity, you can use any database
management system. However, during the
development process or when deploying the website on
a production server, there may be a need to change the
system. This can be easily accomplished through what
is known as Object-Relational Mapping (ORM).
In other words, a WSGI application can interact with
any type of database through the Django ORM's API
interface. And the code at the WSGI application level
remains universal.
By default, Django is configured to work with SQLite.
In the context of our project, we will leave it as is. You
can view the current database configuration in the
configuration package, in the settings.py file, within the
'DATABASES' dictionary.

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Connecting to other databases is done within the same
dictionary. The key is to have the appropriate driver for
interaction with them.
Let's add the first model to our program, which will
describe the "traveler" table for storing information.
I've chosen the following database model structure.
Yours may be different. Since our website is related to
tourism, it's practical to name our models based on the
context. Let's name the first model "dir_travel," which
corresponds to the chosen location that is likely to
interest website users.

dir travel
id: Integer, primary key
title: Varchar
content: Text
photo: Image
timecreate: DateTime
time update: DateTime
is_puplished: Boolean

Let me provide a translation of the explanation and


instructions you provided:
We should clarify this structure a bit. Here,
"id" is the primary key, which accepts unique numerical
values.
Essentially, it serves as the identifier for a record.
"title" is the article's title as a string with a specified
number of characters.
"content" is the text.
"photo" is a field for linking to the primary photo of the
post.
"time_create" and "time_update" represent the time
of article creation and the time of its last update.
"is_published" is a boolean value that can be either
True or False, depending on whether the post is
published or not.
To create a table in our database with this structure, it's
sufficient to define a class with these fields and then
perform a migration.
So, let's define a class for this model. To do this, go to
your project and open the models.py file. In this file,
we will store all our models.
This file is currently empty and contains only the line
"from django.db import models," which imports the
models package. This package contains basic model
classes that can be used as a basis for creating your
own models.
Let's create a "Dir_travel" table that will contain
information about tourist locations.

from django.db import models

class Dir_travel(models.Model):
title = models.CharField(max_length=255)
content = models.TextField(blank=True)
photo =
models.ImageField(upload_to="photos/%Y/%m/%d/")
timecreate =
models.DateTimeField(auto_now_add=True)
timeupdate =
models.DateTimeField(auto_now=True)
is_puplished = models.BooleanField(default=True)

0 Project ▼ © Z "I" C — fi settings.py X fi views.py X base.html X fi urls.py index.html fi mod'


v M traveler
from django.db import models
ifa about.html
2
iTm base.html
3 --.class Dir_travel(models. Model):
m index.html
fi_init_py
title - models.CharField( iax_lengtti=255)

fi admin.py content = models. TextFietd (blar>k=True)

fi apps py photo = models. ImageField(upload_to="photos/%Y/%m/SSd/")

models.py time_create = models. []ateTimeField (auto_now_ad =Tnoe)


ij. tests, py time_update = models . DateTimeField(auto_noi.=Tpue)
lj.urls.py is_puplished = models.BcoleanField(defaul =Troe)
i^views.py

v El travels 11 ®t <■ def (self):


fi initpy
return self.title
fiasgi.py
fi setting s.py
fi urls.py 14

The class name is created by us.


Next, we inherit this class from the base class
models.Model.
There is no 'id' field defined in this class anywhere. The
reason is that this field is already automatically defined
in the base Model class. This field is standard and
exists in all model classes. To avoid specifying it
redundantly in each class, developers have included it
in the base class.
Next, we define the 'title' field as a reference to an
instance of CharField(max_length=255), which
defines a text field with a maximum length of 255
characters.
You can find more detailed information about field types
by following the link
https://django.fun/ru/docs/django/4.0/ref/models/fields/
Next, we define the 'content' field as a reference to
an instance of TextField(blank=True), which contains
more extensive textual information. It has the
blank=True parameter, which means that this field
can be left empty.
The 'photo' field stores a reference to our photograph.
This field is defined through the class
ImageField(upload_to="photos/%Y/%m/%d/"), and
it has the upload_to parameter. This parameter
specifies the directory and subdirectory where we will
upload our images. We can define these directories
using a template like "photos/%Y/%m/%d/".
"photos/%Y/%m/%d/" - The current directory is
"photos," followed by %Y for the year, %m for the
month, and %d for the day. As a result, our photos
will be evenly distributed into folders, with new photos
being uploaded to their respective folders each day.
This structure makes sense in high-traffic projects.
The fields 'time_create' and 'time_update' represent
the creation time and the time of article editing,
respectively.
We define these fields using the
DateTimeField(auto_now=True) class, and this
parameter means that if it is set to True, the
'time_create' field will take the current time at the
moment of adding a new record and will not change
afterward.
When we specify auto_now=True, the 'time_update'
field will change every time we make any changes to
that record.
When we add a new record, the 'is_published' field
will, by default, take the value True.
The sequence of these fields in the table by default will
be the same as in this class.
To make this model work, you need to configure some
settings for the 'photo' field. This field will store only
the path to the database table. In order for Django to
perform automatic file uploads and generate a path to
it, you need to define and configure the following
constants: MEDIA_ROOT and MEDIA_URL.
You can find detailed information on how to configure
these parameters in the official Django documentation
https://docs.djangoproject.com/en/4.1/topics/files/
So, let's open the settings.py file in the configuration
package, and at the very bottom of this file, add
descriptions for these two constants:
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
MEDIA_ROOT will point to the 'media' folder located in
the current directory of our project, BASE_DIR (which
determines the current directory of our project). A
subdirectory 'media' will be added to this working
folder, where all our uploaded files will be located. To
make everything work, it's necessary to import the os
module.
M travels sources root, C:\Python\Django\travels

v travels USE.I18N = True


v Dl traveler
> El migrations USE.TZ - True
ft_init_.py
ft admin, py
116
ftappspy
Y# Static files (CSS, JavaScript, Images)
ft models.py
h# https://docs.djanqopreject.com/en/4.1/howto/static-files/
ft tests, py
ft tidspy

views, py I 1ZU STATIC_URL = 'static/'


v El travels
ft_init_.py y# Default primary key field type
ftasgi.py 123 t# https://docs.djanooDroiect.eom/en/4.1/ref/settings/#default-auto-field
[£■ settings.py
ft tris.py
DEFAllLT_AUTO_FIELD = ' django. db. models. BigAutoField '
wsgi.py
db.sqlite3
e5> manage.py
■ 1ZO MEDIA_ROOT = os.path.join(BASE_DIRr 'media')
> ta venv
lllli External Libraries 129 MEDIA-URL = '/media/'

ta travels Django settings for travels project.


v Ea traveler
> El migrations
Generated by 'django-admin startproject' using Django 4.1.1.
j£_init_.py
t5> admin.py
6 For more information on this file, see
j&apps.py
7 https://docs.djangoproject. com/en/4.1/topics/settinqs/
models, py
&tests,py
fourls.py 9 For the full list of settings and their values, see
r£, views,py 10 https://docs.djangoproject.com/en/4.1/ref/settings/
v El travels
I§i_init_.py
l5.asgi.py
” I.
13 yfrom pathlib import Path
[5. settings.py
14 - import os
frurls.py

The constant MEDIA_URL will add the prefix '/media/'


to the URLs of graphic files. And one more thing!
During the debugging process, we need to simulate
the operation of a real server to retrieve previously
uploaded files and pass them to our application. To do
this, in the configuration package, open the urls.py file,
and after the urlpatterns collection, add the following

if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
In debug mode, we add to the routes
urlpatterns = [
pathCadmin/', admin.site.urls),
path(", include('traveler.urls')),
]

In debug mode, we add another route. To make this


work, we import the static module and settings

from django.contrib import admin


from django.urls import path, include
from traveler.views import index

from django.conf.urls.static import static


from travels import settings

urlpatterns = [
path('admin/', admin.site.urls),
path(", include('traveler.urls')),
]

if settings.DEBUG:
urlpatterns + = static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)

to travels sources root, C:\Python\Django\travels

v to travels ^from django.contrib import admin


v ca traveler
from django.urls import path, include
> Ell migrations
from traveler.views import index
S._init_,py
[^.□dmin.py
S-appspy
from django.conf.urls.static import static
from travels import settings
models.py
& testspy
F^urls.py
views, py <>urtpatterns = [
v to travels path('admin/1, admin.site.urls),
&_init_,py path('', included'traveler.urls')),
f^asgi.py
]
[£, settings.py
&urls.py
if settings.DEBUG:
^wsgi.py
urlpatterns += static(settings .MEDIA_URL, document_root=settings.MEDIA_ROOT)
13 db.sqlite3

manage.py
Now we can create a database table based on the
created model. For this purpose, there is a mechanism
called "migrations" for databases.
Migrations are Python modules that contain sets of
ORM-level commands. When you execute a migration
file, it automatically creates new tables or modifies
existing ones in the database, as well as their
relationships.

Each new migration file is placed in the "migrations"


folder of our application.
The structure of tables in our database is created based
on these migration files.
We go to our terminal and to the command line (venv)
PS C:\Python\Django\travels\travels>
We add python manage.py makemigrations

So, we have our first migration. To work with graphic


files, it's better to install the Pillow package right away
using the command python -m pip install Pillow
Windows PowerShell
(C/ 2016 Microsoft Corporation. All rights reserved,

(venv) PS C:\Python\Django\travels> cd travels


(venv) PS C:\Python\Django\travels\travels> python manage.py makemigrations
Migrations for 'traveler':
traveler\migrations\0001_initial.py
- Create model Dir_travel
(venv) PS C:\Python\Django\travels\travels> python -m pip install Pillow
Collecting Pillow
Downloading Pillow-9.2.Q-cp310-cp310-win_amd64.whl (3.3 MB)
IHIH^MIIII^nill^HIIIIII 3.3 MB 1.1 MB/s
Installing collected packages: Pillow
Successfully installed Pillow-9.2.0
WARNING: You are using pip version 21.3.1; however, version 22.2.2 is available.
You should consider upgrading via the 'C:\Python\Django\travels\venv\Scripts\python.exe -m pip install --upgrade pip' command,
(venv) PS C:\Python\Django\travels\travels> Q

And at the same time, let's update the package


manager pip
python -m pip install --upgrade pip
And as you can see, we now have a migration file

v k travels sources root C:\Pytl


v k travels
v k traveler
v k migrations
0001 Jnitial.py
ft _init_.py
ft_init_.py

admin.py
&apps.py

We'll create the table directly in the database by


running the command
python manage.py migrate

The migration process in our terminal console looks like


this
(venv) PS C:\Python\Django\travels\travels> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, traveler
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.000A_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.00O9_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
Applying traveler.0001_initial... OK
(venv) PS C:\Python\Django\travels\travels> [

_K(
Templates
We will discuss where to store templates and how to
connect them in this section. You can find more detailed
information about templates by visiting the official
Django website
https://django.fun/ru/docs/django/4.0/topics/temp
lates/
Let's assume that as our main page, we want to use a
template that is stored in a file index.html
To begin, let's import the built-in Django template
engine. Next, go to our project and open the views.py
file. This is the file where we will place all our
templates.

n traveler
-from django.shortcuts import render, redirect
v U migrations
& 0001Jnitial.py
i§._init_.py
def index(request):

S_init_.py return HttpResponse("The traveler app page")


f&admin.py
45.apps.py Qdef categories(request, order_id):
45> models,py return HttpResponse(f"<hl>CategoiyArticles</hl><p>{order_id}-</p>")
tests, py
rC urls.py def archive (request, year):
i5> views.py
if int(year) > 2022:
ea travels
return redirect(1 home', permanent=True)
l5._init_.py
return HttpResponse (fchlsArive byyear</hl><p>{year}</p>")
l5<asgi.py
r5> settings.py

f5> urls.py
t5> wsgi.py

As we can see, the render function is already imported


here. This function is the built-in template engine in
Django, and it processes our templates.
Next, to make the index function display the desired
template instead of the HttpResponse class, we'll use
the render function. We'll pass the HttpRequest object
as the first parameter and specify the path to the
template as the second parameter, leaving it empty for
now.
Now, where should we place our template files for our
application? They should be located in the "templates"
folder of our "traveler" application. However, there's
an important detail to consider. When building the
entire project and deploying it on a live server, all
templates from different applications are placed in a
single "templates" folder, but this folder is shared
across the entire project. If there are two identical files
in different applications, the first one encountered will
be used. This can create some difficulties.
To avoid this, within the "templates" folder of our
application, "traveler," we'll create another subfolder
with the name of our application, "traveler." Inside this
subfolder, we'll place our template files
Let's create a template file index.html
Please note the file extension of this file.

Pay attention to the UTF-8 encoding. Python itself


works with this encoding, so using other encodings may
lead to issues.
We've created our first, very simple template. Now,
let's go back to the views.py file and specify the path
to our template in the index function.

from django.shortcuts import render, redirect

def index(request):
return render(request, 'traveler/index.html')

The thing is, Django by default looks for all templates


in the "templates" folder, and anything within that
folder needs to be specified by us.
Let's start our server and check the functionality of our
modified application.

© Main page x i

<- -> C <D 127.0.0.1:8000

Main page

In this way, we've separated the code of our application


from the actual templates.
In the future, we can change the template code without
altering anything in the application itself. This is the
essence of templates: to separate the application's
logic from its presentation.

As an example, let's add another template that


contains information about the website and name it
about.html.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> About the website</title>
</head>
<body>
<h1> About the website</h1>
</body>
</html>

— settings.py X views.py < iff index.html


O
Q Project ▼
©
H

■' to travels sources root, C:\Python\Django\travels 1 <!DOCTYPE html>


v to travels <html lang="en">
v to traveler 7 R<head>
v to migrations Zi <meta charset="UTF-8">
& OOOIJnitial.py r <tit'Le>Aboirt the site
_init_.py
A E)</head>
v to templates
v to traveler
H<body>
m about.html <hl> About the site </hl>

index.html </body>
rt._init_.py 10 </html>
admin.py

In the views.py file, we will define the corresponding


view function for this template.

def about(request):
return render(request, 'traveler/about.html')
travels sources root, C:\Python\Django\travels ^from django.http import HttpResponse
v travels Afrom django.shortcuts import render, redirect
- El traveler
v El migrations
c>def index(request):
0001JnitiaLpy
return render(request, 'traveter/index.html' )
fi._init_.py
v M templates
v M traveler
Hdef about(request):

about.html return render(request, 1traveler/about.html')


index.html
S-init_.py c>def categories(request, order_id):
fiadmin.py return HttpResponse(f "<hl> Category Articles. </hl><p>{order_id}</p>")
fiapps.py
fi. models.py
Hdef archive (request, year):
fi- tests.py
® if int(year) > 2022:
fi-urls.py
return redirect(’home’, permanent=True)
fi.views.py
return Http Re sponse(f "<hl> Archive by years</hlxp>{year}</p>")
v El travels
__________ »£■ i^it ny_________________________

We will also define a route for this view function. Open


the urls.py file in the application and add the following

urlpatterns = [
path(", index, name='home'),
path('about/', about, name='about'),]
H Project ▼ Q £ T $ settings.py X views.py < urls.py index.html

travels root, C:\Python\Django\travels 1 import ...


v travels
- El traveler Eurlpatterns = [
- El migrations pathf11, index, name=1 home 1),
OOOIJnitial.py
path(1 about/1, about, name='about1),
& jnit_.py
v ta templates 8 6

Let's start the web server (using the command python


manage.py runserver within your virtual
environment), and then enter the following URL in your
browser's address bar: http://127.0.0.1:8000/about/

<- -> O <£> 127.0.0.1:8000/about/

About the site

We got the expected result.


In the simplest form, this is how you can add templates
to your application.

If you look at the index.html and about.html files,


they are just HTML pages. In these files, you can
include constructs to display information, for example,
from a database.
As an example, let's make it so that a specific title is
displayed for each page. Instead of <title> Main Page
</title>, we'll use <title>{{ title }}</title> in the
index.html file. We'll do the same in the about.html
file.

Where will this variable come from?


If we go to the views.py file, we can pass parameters
to these templates. Parameters are passed as the third
argument in the form of a dictionary, where we specify
the key and value.

def index(request):
return render(request, 'traveler/index.html', {'title':'
Main Page '})

def about(request):
return render(request, 'traveler/about.html', {'title':
''About the Website'})

to travels sources root C:\Python\Django\travels Hfrom django.http import HttpResponse


v to travels ^from django.shortcuts import render, redirect
v to traveler
v to migrations
def index(request):
0001Jnitial.py
return render (request, 'traveler/index.html', {'title': TjiaBHas CTpaHwua ’})
rS I nit .py
v totemplates
v to traveler
def about(request):

about.html return render(request, 'traveler/about.html', {'title': '0 cawTe'})


|7^index.html
_ init_.py 10 def categories(request, order„id):
admin.py return HttpResponse(f"<hl>categories.</hlxp>{order_id}</p>")
r§»apps.py
[£> models,py
def archive (request, year):
tests.py
if int(year) > 2022:
f&urls.py
return redirect('home', permanent=True)
views,py
return HttpResponse(f"<hl>Archivebyyears</hlxp>{year]-</p>")
- to travels
_ init_.py

Or we can make it more complex. For example, a list of


the main menu
menu = ["About the website", "Add an article",
"Feedback", "Log in"]

[■] Project ▼ O -z- Cl settings,py x views.py urls.py index.html x abouthtml

v to travels sources roof C:\Python\Django\travels Rfrom django.http import HttpResponse


v to travels Hfrom django.shortcuts import render, redirect
to traveler
-- Ea migrations menu = [”0 Site”, "Add article”, "Feedback”, "Login11 ]
[L 0001Jnitial.py

We want to display this list - the menu list, for example,


on the main page.
You can do this as follows.
In the views.py file of our application, instead of the
line

def index(request):
return render(request, 'traveler/index.html', {'title': Main
page})

nponui±ieM
def index(request):
return render(request, 'traveler/index.html', {'menu':
menu, 'title': Main page})

or more precisely, we'll add.


'menu': menu. The first parameter - directly the menu
and will have a link to the list that we defined earlier,
and the second
'title': ‘Main page’ - link to the home page.

Now, in the index.html template, we will use the menu


parameter and display this list. To do this, go to the
index.html file and in the <body></body> section,
display this list

<!DOCTYPE html>
<html lang='"en'">
<head>
<meta charset='"UTF-8'">
<title>{{ title }}</title>
</head>
<body>
{% for m in menu %}
<li>{{m}}</li>
{% endfor %}
</body>
</html>

We used the 'for' tag to iterate through the menu


parameters to display the value of 'm,' i.e., the strings
from menu = ["About the website", "Add an
article", "Feedback", "Log in"] as a list.
We will start the runserver, open the browser, and
refresh the main page.

Main page x

<- -> C <D 127.0.0.1:8000

• About the site


• Add article
• Feedback
• To come in

We will do the same for the page about.html


<- -> C (*> 127.0.0.1:8000/about/

• About the site


• Add article
• Feedback
• To come in

If you take a closer look, you'll notice that index.html


and about.html essentially have the same content,
meaning the DRY principle (Don't Repeat Yourself) is
being violated here. To address this issue, a common
approach is to create a base template for the overall
site's pages, which is then extended by individual page
templates.
We will create a base template for the site and name it
base.html.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
{% block mainmenu %}
<ul>
{% for m in menu %}
<li>{{m}}</li>
{% endfor %}
</ul>
{% endblock mainmenu %}

{% block content %}
{% endblock %}
</body>
</html>

travels . .. es root, C:\Python\Django\travels <!DOCTYPE html>


v M travels <html lang="en">
v b traveler <head>
v £■ migrations
<meta charset="UTF-8">
ft OOOIJnitial.py
<title>-{{ title }}</title>
ft _init_.py
</head>
v M templates
<body>
v traveler
(fjabout.html -(% block mainmenu %}
ift base.html <ul>
ift mdex.html -(% for m in menu %}
ft_init_.py <li>«m}}</li>
ft admin.py {% endfor %}
ftapps.py </ul>
ft models.py
endblock mainmenu %}
fttests.py
fturls.py
block content %}
ft views.py
{% endblock %}
v travels
ft_init_.py <jfcody>
ftasgi.py </html>

Next, in the index.html file, instead of

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
{% for m in menu %}
<li>{{m}}</li>
{% endfor %}
</body>
</html>

We will specify it as follows

{% extends 'traveler/base.html' %}

{% block content %}
<h1>{{title}}</h1>
<p>The content of the page</p>
{% endblock %}

{% extends 'traveler/base.html' %} - we are


extending the base template, base.html, and within
the content block {% block content %} {%
endblock %}, we include the following information.

<h1>{{title}}</h1>
<p>The content of the page</p>

H Project ▼ © 2. ~ settings.py x views.py base.html x

B travels sources root, C:\Python\Django\travels {% extends 1traveter/base.html' %}


v Bl travels
v U traveler ■{% block content %}
v Di migrations
<hl>{-{title}}</hl>
0001.initial.py
< p > Page content< / p >
& init.py
{% endblock %}
_______ v templates_____________________________
We will do the same for about.html. We reload our
page and obtain...

<- -> O d> 127.0.0.1:8000/about/

• About the site


• Add article
• Feedback
• To come in

About the site


Page content

In this way, by using template inheritance or extending


a base template, we eliminate code duplication, which
is the recommended practice.
Let's proceed with reading data from the 'traveler'
table and displaying a list of articles on the main page
of our site. First, we'll go to the views.py file and
import our models.

from django.http import HttpResponse


from django.shortcuts import render, redirect

from .models import *

...we access the models.py file with our models and


import all models. Instead of specifying each model
individually, we can import all models using '*'.
Next, in the index function, we will add the line posts =
Dir_travel.objects.all() - which selects all the records
in the Dir_travel table and stores the references to
these records in the 'posts' variable, which we will then
pass to the template.

def index(request):
posts = Dir_travel.objects.all()
return render(request, 'traveler/index.html', { 'posts':
posts, 'menu': menu, 'title': Main page '})

travels travels traveler p^views.py 4c- Current I

H Project ▼ O i. -J- settings.py views.py base.html [£> urls.py index.html x models.py about.html

ta travels sources root, C:\Pyttion\Django\travels 1 Rfrom django.http import HttpResponse


M travels 2 from django.shortcuts import render, redirect
Ea traveler
v Emigrations
4 yfrom .models import *
[£, 0001Jnitial.py
ft_init_.py
6 I menu = [ "0 site","Add article’’, "Feedback", "Login" ]
v fc templates
v E traveler
ifw about.html 8 ydef index(request):
ifft base.html posts = Dir_travel. objects. aH0
ifft index.html return render(request, 'traveler/index.html' , {'posts': posts, 'menu': menu, 'title': 'HomePage1

Open the index.html file, and instead of the paragraph


<p>Page content</p>, we will create a list of our
articles.

{% extends 'traveler/base.html' %}

{% block content %}
<h1>{{title}}</h1>
<ul>
{% for p in posts %}
<li>
<h2>
{{p.title}}
</h2>
<h2>
{{p.content}}
</h2>
<hr>
</li>
{% endfor %}
</ul>

{% endblock %}

In this code, we iterate through all objects of our model


using a loop and display only the 'title' and 'content'.
To test the functionality of this code, start the web
server using the known command 'python manage.py
runserver' and refresh the page
http://127.0.0.1:8000/
Nothing surprising - we don't have any records yet. We
only have the model, but the table is empty.
Creating an Admin Panel
To launch the admin panel, you need to enter into the
browser
http://127.0.0.1:8000/admin/
After that, a page will appear

The admin panel is currently displayed in English. If you


want to change the language, then go to the
configuration package "travels" and open the
"settings.py" file. In it, find the variable
"LANGUAGE_CODE = 'en-us'." By default, it is set to
English. You can leave it as is, but if necessary, you can
change the language, for example, to Russian..
The web server is restarted, you refresh the page, and
you will see the admin panel in Russian. Additionally,
the entire Django framework will be localized in
Russian. The same procedure can be followed to
change the language to other languages as well.

<- -> C © 127.0.0.1:8000/admin/login/?next=/admin/

AAMnHMCTpupoeaHne Django

Wmh nojib3CBare;iA:

Flaponb:

Bomtm

This admin panel allows you to handle all standard


tasks. This includes creating users with different
permissions, displaying all of our applications, adding,
modifying, deleting records, and more.
So, to log in, you need to enter a username and
password, but we don't have them yet. First, it is
necessary to create a superuser, also known as the
site administrator.

To do this, go to the project's terminal and execute the


command

Python manage.py createsuperuser

(venv) PS C:\Python\Django\travels\travels> Python manage.py createsuperuser


Username (leave blank to use 'user'): [

Let's enter the username. For example, "root."

Username (leave blank to use 'user'): root


E-mail address : Fl

An email address, for example [email protected]

Username Cleave blank to use 'user'): root): root


E-mail address : rootfagmail.coin
Password:
Password (again):

Next, we come up with a password and enter it again.


For educational purposes, we use simple and easy-to-
remember names, email addresses, and passwords.
However, when deploying on a production server, it's
important to create non-standard and complex
usernames and passwords, as well as use a valid email
address for receiving administrative information.

Superuser created successfully.


(venv) PS C:\Python\Django\travels\travels>

The superuser has been created. Now we can enter the


username and password in the admin panel.

And we log into the admin panel... Don't forget to


restart the server!
On the main page, we see two registered applications -
these are "Groups" and "Users." We can create new
users and assign them specific roles.

Forming groups for users

Django administration WELCOME, ROOT. OPEN SITE / CHANGE PASSWORD / EXIT

Home Users and groups Groups

However, our "Dir_travel" application is not listed here


because it needs to be registered with this admin
panel. To do this, open the project's "admin.py" file
and specify the registration for our application. In this
file, our model should be accessible, so we import it
(import all models at once).

from .models import *


Next, let's add the following line
admin.site.register(Dir_travel)

The entire code:


from django.contrib import admin
from .models import *
admin.site.register(Dir_travel)

Let's refresh the page in the browser

Add Edit

And we can see that our model has appeared. If you


click on the model's tab, you'll see that we have no
articles. Let's create a few articles. Click on the "Add"
button and fill in the fields

An And click "Save."


Django It suggests that we haven't filled in all the
fields

Obligatory field.

Article text: || Select file | File not selected

□ Is puplished

Save and add another object B Save and continue editing


I
Let's select a file with a photo

<- -> 0 O 127.0.0.1:8000/admin/traveler/dir_travel/add/

Operational Error at /admin/traveler/dir_travel/add/


table traveler dir travel has no column named title
Request Method: POST
Request URL: http://127.0.0.1:8000/admin/traveler/dir_travel/add/
Django Version: 4.1.1
Exception Type: OperationalError
Exception Value: table traveler_dir_travel has no column named title

Exception Location: C:\Python\Django\travels\venv\lib\site-packages\django\db\backends\sqlite3\base.py, line 357, in execute


Raised during: django.contrib.admin.options.add view
Python Executable: C:\Python\Django\travels\venv\Scripts\python.exe
Python Version: 3.10.7
Python Path: ['C:\\Python\\Django\\travels\\travels ’,
'C:\\Python\\python310.zip'j
'C:\\Python\\DLLs"3
'C:\\Python\\lib’,
'C:\\Python',
'C:\\Python\\Django\\travels\\venv',
'C:\\Python\\Django\\travels\\venv\\lib\\site-packages']
Server time: Wed, 12 Oct 2022 11:23:09 +0000

What is the cause of this error?


You can spend a long time figuring out where the
mistake was made. In my case, I simply deleted the
database file db.sqlite3 and the migration file
0001_initial.py, and then re-entered the commands in
the console
python manage.py makemigrations
then
python manage.py migrate
You also need to re-create the superuser using the
command
Python manage.py createsuperuser
Let's access the admin panel and create our first post
by filling in all the fields. The result will look as follows:
■About tMSIB
* Add article
• Feedback
• To come in

Main page

• Poland

Poland,[b] officially the Republic of Poland,[c] is a country in Central Europe. It is divided into 16 administrative provinces called voivodeships, covering an area of 312,696 kni2 (120,733 sq mi).
Poland has a population of over 38 million and is the fifth-most populous member state of the European Union.[12] Warsaw is the nation's capital and largest metropolis. Other major cities
include Krakow, Wroclaw, Lodz, Poznan, Gdansk, and Szczecin. Poland has a temperate transitional climate. Its territory extends from the Baltic Sea in the north to the Sudeten and Carpathian
Mountains in the south. The country is bordered by Lithuania and Russia to the northeast,[d] Belarus and Ukraine to the east, Slovakia and the Czech Republic to the south, and Germany to the
west. Poland also shares maritime boundaries with Denmark and Sweden.

We will display the photos on the page later. Similarly,


we will create a few more posts.
• Add article
• Feedback

Main page

• Poland

Poland,[b] officially the Republic of Poland,[c] is a countiy in Central Europe. It is divided into 16 administrative provinces called voivodeships, covering an area of 312,696 km2 (120,733 sq mi).
Poland has a population of over 38 million and is the fifth-most populous member state of the European Union.[12] Warsaw is the nation's capital and largest metropolis. Other major cities
include Krakow, Wroclaw, Lodz, Poznan, Gdansk, and Szczecin. Poland has a temperate transitional climate. Its territory extends from the Baltic Sea in the north to the Sudeten and Carpathian
Mountains in the south. The countiy is bordered by Lithuania and Russia to the northeast,[d] Belarus and Ukraine to the east, Slovakia and the Czech Republic to the south, and Germany to the
west. Poland also shares maritime boundaries with Denmark and Sweden.

Ukraine

Ukraine (Ukrainian: ykpama, romanized: Ukraina, pronounced [ukrn jinn] (listen)) is a countiy in Eastern Europe. It is the second-largest European countiy after Russia, which it borders to the
east and northeast.[a][ll] Ukraine covers approximately 600,000 square kilometres (230,000 sq mi).[b] Prior to the ongoing Russo-Ukrainian War, it was the eighth-most populous countiy in
Europe, with a population of around 41 million people.[c][6] It is also bordered by Belarus to the north; by Poland, Slovakia, and Hungaiy to the west; and by Romania and Moldova[d] to the
southwest; with a coastline along the Black Sea and the Sea ofAzov to the south and southeast.[e] Kyiv is the nation's capital and largest city. The countiy's national language is Ukrainian, an g x
most people are also fluent in Russian.[14] '*

Germany

Germany (German: Deutschland, pronounced [ davtjlant] (listen)), officially the Federal Republic of Germany,[f] is a countiy in Central Europe. It is the second most populous countiy in Europe
after Russia, and the most populous member state of the European Union. Germany is situated between the Baltic and North seas to the north, and the Alps to the south; it covers an area of
357,022 square kilometres (137,847 sq mi), with a population of almost 84 million within its 16 constituent states. Germany borders Denmark to the north, Poland and the Czech Republic to the
east, Austria and Switzerland to the south, and France, Luxembourg, Belgium, and the Netherlands to the west. The nation's capital and largest city by population is Berlin and its financial centre
is Frankfurt; the largest urban area is the Ruhr.

So, we have a Dir_travel model. Let's configure our admin


panel to make it more user-friendly. The first thing we'll do is
change the name Dir_travel to 'All Countries.' How to do
this? We go to the models.py file and in the Dir_travel
class, we add an inner class Meta
class Meta:
verbose name = "Bee crpaHH"
H Project ▼ © £ -r 0 — models.py traveler\urls.py X travels\urls.py views.py
moaeis.py
from django.db import models
& tests.py
tj. urls.py

views,py
v Ea travels 4 Rclass Dir_travel(models.Model):
(Ji_irit_,py title = models.CharField(max_length=255)
ft asgi.py content = models.TextField(blank-True)
settings,py photo - models. ImageField(l^pload_to=' lphotos/%Y/%m/%d/,l)
S urls.py time_create = models.DateTimeField(auto_now_add-True)
wsgi.py time_update = models.DateTimeField(auto_now=True)
H db.sqliteS
is_puplished = models. BooleanField (clef ault=True)
manage,py
> M venv
L2 ©T 3 def (self):
' Hill External Libraries
v *3* < Python 3.10 (travels) (2) > C:\Python\Django\travek
return self.title

> lllh Binary Skeletons

> Uni is L5 H class Meta:


> lllll Extended Definitions L6 Id verbose_name = " All countries "
> Lib 17
M Python

This is a special class used by the admin panel to configure


the Dir_travel model. Lets go to the admin panel and
refresh/update it.

The letter 's' is added automatically by Django to


convert the singular form into the plural form. This can
be corrected by specifying the second attribute
class Meta:
verbose_name = " All Countries'"
verbose_name_plural = "All Countries"

We refresh the browser page, and as we can see, the


letter 's' is gone.

Next, let's create sorting of records by their creation


time.
In the same Meta subclass, we add another attribute
and specify the fields by which sorting will occur. You
can specify multiple fields, and sorting will be
performed from left to right.

class Meta:
verbose_name = "All Countries"
verbose_name_plural = "All Countries"
ordering = ['time_create', 'title']
Let's go to the admin panel and refresh the page

We will see that the order has changed. We can reverse


the order by adding a '-' sign. For example
ordering = ['-time_create', 'title']
And, if we refresh, we will get reverse sorting.
This sorting is applied not only to the admin panel but
also to our web page. Let's go back to the admin panel
and continue making improvements. We will change
the name 'TRAVELER' to 'My travels.' To do this, open
the apps.py file and add the following code in the
TravelerConfig class

verbose_name = " TRAVELER"

class TravelerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'traveler'
verbose_name = "TRAVELER"

This class is used for configuring the entire


application.
Let's go to the admin panel and refresh.

This change will work if we register our application in


the settings.py file in exactly this way:

'traveler.apps.TravelerCon fig'

If you register it simply as 'traveler', it won't work.


Keep that in mind.
os index.html
ft _init_.py
AINSTALLED.APPS = [
ft admin.py
ft appspy 'django.contrib.admin',
ft models.py 'django.contrib.auth',
ft tests.py 'django.contrib.contenttypes',
ft urls.py 'django.contrib.sessions',
ft views.py 9 'django.contrib.messages',
v El travels 'django.contrib.staticfiles',
ft _init_.py
'traveler.apps.TravelerConfig'
ft asgi.py
S]
ft settings.py

Next, let's add more fields in the list of articles, not just
the title (such as publication time, publication flag,
etc.). Open the admin.py file and add the following:

from django.contrib import admin


from .models import *

class Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'timecreate', 'photo')
list_display_links = ('id', 'title')
search fields = ('title', 'content')

Please note that if in the line 'search_fields = ('title',


'content')' we were searching by a single field, such as
'title,' then we would need to write 'search_fields =
('title', )'. That is, after 'title,' it's essential to add a
comma, as we are passing a tuple. Without the comma,
it would no longer be a tuple but a string

admin.site.register(Dir_travel)
list_display" contains a list of all the fields we want to
see in our admin panel.
"list_display_links" contains the fields on which we
can click to go to the corresponding article for editing.
"search_fields" specifies the fields by which we can
search for specific information.

And now, we specify this class as the second parameter


when registering it

admin.site.register(Dir_travel, Dir_travelAdmin)

Let's go to the browser and refresh the admin panel.


Now, let's translate the headings into Russian. To do
this, open models.py and add a few lines in the
Dir_travel class

class Dir_travel(models.Model):
title = models.CharField(max_length=255,
verbose_name = "Heading")
content = models.TextField(blank=True, verbose_name
= "Article text")
photo =
models.ImageField(upload_to="photos/%Y/%m/%d/",
verbosename = "Photo")
time_create =
models.DateTimeField(auto_now_add=True,
verbose_name = "Time of creation ")
time_update = models.DateTimeField(auto_now=True,
verbose_name = "Change time")
is_puplished = models.BooleanField(default=True)

Let's go to the browser and update.


Connecting static files

Let's consider the possibility of connecting static


files, such as CSS, JavaScript, and so on. Our application
can operate in two modes: debugging mode on a test
web server and production mode on a real server. In
debugging mode, Django looks for static files in all
subdirectories named "static" in our applications. If
there are multiple such directories, static files will be
searched in all of them. Non-standard paths for static
files can also be defined, where static files will be
searched.
In production mode, the real server will take all static
files from the "static" folder located in the project's
root directory. How does this folder with all these files
appear? To achieve this, a special command is executed
when preparing the project for production:
Copy code
python manage.py collectstatic
After running this command, all static files scattered
across different "static" directories located at various
paths are collected into a common "static" folder for
the entire project.
To ensure that all this functionality works correctly, you
need to define the following three constants in the
configuration package:
STATIC_URL - the URL prefix for static files.
STATIC_ROOT - the path to the common static folder
used by the real web server.
STATICFILES_DIRS - a list of additional paths to static
files used for collection and debugging mode.
So, in the configuration package, open the settings.py
file and add all these constants at the bottom.
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = []

The first constant is already in place, and the last one is


empty for us because there are no non-standard paths.

to templates H# Static files (CSS, JavaScript, Images)


v to traveler A# https://docs.djanqopreject.com/en/4.1/howto/static-files/
iTm about.html
,7m base.html
STATIC_URL = '/static/'
[7m index.html
STATIC-ROOT - os.path.join(BASE.DIR, 'static')
ft _init_.py
STATICFILES_DIRS = []
admin.py

Next, let's create a "static" folder in our "traveler"


application.
In the same folder, create a directory named "traveler"
for templates to avoid naming conflicts.
In this subdirectory, create the following directories:
"css" for storing cascading style sheets (CSS).
"js" for storing JavaScript files.
"images" for storing image files.
Within the "css" directory, create a file named
"styles.css" to hold the style rules for page formatting.
We won't go into detail about writing the style rules
here, as it's a separate topic, but you can simply copy
and paste a pre-made template.

H Project ▼ © ± "T C* models.py x settings.py X styles.css admi

' ta travels sources root, C:\Python\Django\travels html, body {


v M travels font-family: 'Arial1;
> M media margin: Q;
v Bi traveler
padding: 0;
v El migrations
height: 100%;
&0001 Jnitial.py
width: 100%;
tj_init_.py
v ta static
color: #444;
v M traveler }
v M css 9
styles.css a {
M images color: #0059b2;
tajs text-decoration: none;
v M templates
> Bi traveler r
a:hover {
iji_init_.py
color: #CC00O0;
I$> admin.py
text-decoration: underline;
& appspy

Using the same analogy, I prepared and uploaded


image templates to the "images" directory.

Q YouTube
66 OYouTube
blockquote.png btn_yt.png logo.png main.ico share_yt.png smallmenu.png
■ travels sources root C:\Python\Djai
v ■ travels
> ■ media
v El traveler
v El migrations
r^.0001 Jnitial.py
&_init_.py
v ■ static
v M traveler
v ■ css
aa styles.css
v ■ images
blockquote.png
U btn_ytpng

logo.png
main ,ico
share_yt.png
smallmenu.png
■ js

Next, open the base template, base.html, and at the


very beginning of the template, add the tag {% load
static %}.
The load tag loads the static tag, through which we will
connect external files. Then, in the <head></head>
section, we will link the styling file:
<link type="text/css" href="{% static
'traveler/css/styles.css' %}" rel="stylesheet/>.
Here, we specify the path to the styles.css file.
Django will automatically find the styles directory, and
then we specify the path ourselves. Let's see how it will
look. First, start our server and enter the standard URL
in your browser:
http://127.0.0.1:8000/.
C Q 127.0.0.1:8000 Q 14*
• About the ate
• Add article
• Feedback

Main page
□ Moldova

Moldova (Romanian: Republics Moldova), is a landlocked country in Eastern Europe.[17] It is bordered by Romania to the west and Ukraine to the north, easl
Russian puppet state of Transnistria lies across the Dniester on the country's eastern border with Ukraine. Moldova's capital and largest city is Chisinau.

Germany

Germany (German: Deutschland, pronounced [ doYtjlant] (listen)), officially the Federal Republic of Germany,[f] is a country in Central Europe. It is the secon
after Russia, and the most populous member state of the European Union. Germany is situated between the Baltic and North seas to the north, and the Alps
357,022 square kilometres (137,847 sq mi), with a population of almost 84 million within its 16 constituent states. Germany borders Denmark to the north, Pol
east, Austria and Switzerland to the south, and France, Luxembourg, Belgium, and the Netherlands to the west. The nation's capital and largest city by popul
Frankfurt; the largest urban area is the Ruhr.

Ukraine

Ukraine (Ukrainian: YKpaiHa, romanized: UkraTna, pronounced [ukre jine] (listen)) is a country in Eastern Europe. It is the second-largest European country al
and northeast.[a][11] Ukraine covers approximately 600,000 square kilometres (230,000 sq mi).[b] Prior to the ongoing Russo-Ukrainian War, it was the eighth
with a population of around 41 million people.[c][6] It is also bordered by Belarus to the north; by Poland, Slovakia, and Hungary to the west; and by Romani,
with a coastline along the Black Sea and the Sea of Azov to the south and southeast.[e] Kyiv is the nation's capital and largest city. The country’s national lai
are also fluent in Russian.[14]

Poland

Poland,[b] officially the Republic of Poland,[c] is a country in Central Europe. It is divided into 16 administrative provinces called voivodeships, covering an ;
Poland has a population of over 38 million and is the fifth-most populous member state of the European Union.[12] Warsaw is the nation's capital and largesl
Krakow, Wroclaw, Lodz, Poznan, Gdansk, and Szczecin. Poland has a temperate transitional climate. Its territory extends from the Baltic Sea in the north to tl
in the south. The country is bordered by Lithuania and Russia to the northeast,[d] Belarus and Ukraine to the east, Slovakia and the Czech Republic to the s<
also shares maritime boundaries with Denmark and Sweden.

If you look at the code of the loaded page, you will find
that the CSS stylesheet file is included
1

2 <!DOCTYPE html>
3 <html lang="en">
4 <head>
5 <meta charset=,rUT = -8">
6 <title>r/iaBH.nfl crpaHHi4&</title>
7 <link type="text/css" href="/static/traveler/css/5tyles.css" re1="stylesheet" />
8 </head>
9 <body>

Clicking on href="/static/traveler/css/styles.css",
the next thing you will see is the code for our stylesheet
htnulj body {
font-family: 'Arial';
margin: 0;
padding: 0;
height: 130%;
width: 100%;
color: #444;
? '
a {
color: #0059b2;
text-decoration: none;
?
a:hover {
color: #CCfN)00;
text-decaration: underline;
:•
img {max-width: 600px; height: auto;}

ing.img-art ide-left {
max-width: 3f»0px;
oeight: auto;
float: left;
padding: 0 lOpx l®px 0;
:• '

ing.img-art ide-left-thumb {
max-width: 15E>px;
height: auto;
? ’

In this way, we have connected the stylesheet to the


base template using the {% static %} tag.
Next, to style our pages with the help of the stylesheet
and graphic files, we will make some modifications to
the index.html, base.html, and about.html files

Base.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<link type="text/css" href="{% static 'traveler/css/styles.css' %}"
rel="stylesheet" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="shortcut icon" href="{% static 'traveler/images/main.ico' %}"
type="image/x-icon"/>
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
</head>
<body>
<table class="table-page" border=0 cellpadding="0" cellspacing="0">
<tr><td valign=top>
{% block mainmenu %}

<div class="header">
<ul id="mainmenu" class="mainmenu">
<li class="logo"><a href="#"><div class="logo"></div></a></li>
{% for m in menu %}
{% if not forloop.last %}
<li><a href="#">{{m.title}}</a></li>
{% else %}
<li class="last"><a href="#">{{m.title}}</a></li>
{% endif %}
{% endfor %}
</ul>
<div class="clear"></div>
</div>
{% endblock mainmenu %}

<table class="table-content" border="0" cellpadding="0" cellspacing="0">


<tr>
<!--Sidebar cneBa -->
<td valign="top" class="left-chapters">
<ul id="leftchapters">
<li class="selected">All countries</li>
<li><a href="#"></a>Ukraine</li>
<li><a href="#"></a>Poland</li>
<li><a href="#"></a>Moldova</li>
<li><a href="#"></a>Germany</li>
<li><a href="#"></a>Spain</li>
<li class="share">
<p>Our channel</p>
<a class="share-yt" href="#"> </a>
</li>
</ul>
</td>
<!-- End Sidebar -->
<td valign="top" class="content">
<!-- bread crumbs -->
{% block breadcrumbs %}
{% endblock %}

<!-- Content block -->


<div class="content-text">
{% block content %}
{% endblock %}
</div>
<!-- End of content block -->
</td></tr></table>
</td></tr>
<!-- Footer -->
<tr><td valign="top">
<div id="footer">
<p>traveler</p>
</div>
</td></tr></table>
<!-- End Footer -->
</body>
</html>
H Project ▼ © £. -7- O - ft mode‘ls-Py ft settings.py styles.css base.html ft admin.py ft views.py ft urls.py

v1 travels sources root, C:\Python\Django\travels ML {% load static %}


v to travels <!DOCTYPE html>
> to media
3 > <html>
v to traveler
4 <head>
v to migrations
5 <title>{-{title}]-</title>
ft 0001Jnitial.py
6 <link type="text/css" href="{% static 'traveler/css/styles.css' %}" rel="stylesheet" />

to static
7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
v to traveler 8 <link rel="shortcut icon" href="{% static 'traveler/images/main.ico' %}" type=l,image/x-icon,7>
> to CSS 9 <meta name="viewport" content=”width=device-width, initial-scale=1.0">
> to images 10 t </head>
tojs 11 f <body>
v to templates 12 f3<table class="table-page" border=0 cellpadding=l,0" cellspacing=l,0l,>
'■s to traveler
13 S3<trxtd valign=top>
about.html
14 {% block mainmenu %}
fa base.html
15
index.html
ft_init_.py
16 f <div class="header">
ft admin.py 17 <ul id="mainmenu" class=l,mainmenul,>
ft apps.py 18 <li class=l,logo"xa href="#"xdiv class="logo"x/divx/ax/li>
ft models.py 19 {% for m in menu %}
fttests.py 20 {% if not forloop.last %}
ft urls.py 21 <lixa href ="#">■{{m.title}]-</ax/li>
ft views.py
22 {% else %}
v to travels
23 <li class="last"xa href="#">{{m.title}-}-</ax/li>
ft _init_.py
24 {% endif %}
ft asgi.py
25 {% endfor %}
ft settings.py
26 t t/uli
ft urls.py
ftwsgi.py | 27 <div class="clear"x/div>
|| dbsqlite3 28 E-1 </div>
ft manage.py 29 {% endblock mainmenu %}

to] Project ▼ © — ft models.py ft settings.py gg stylesrss ft base.html ft admin.py

v B travels sources root, C:\Python\Django\travels 1 31 Retable class='rtable-content" border="0" cellpadding="0"


v H travels 1 32 R<tr>
> media 1 33 <!--Sidebar left

v EBtraveler I 34 i <td valign="top" class="left-chapters">


v Di migrations 1I 35 JL
H
, - . .
<ul id-”leftchapters">
ft 0001Jnitial.py
ft_init_.py <11 class-"selected "> ah countries </li>
<lixa href="#"x/a> Ukraine </li>
v Bl static
v Bi traveler <ll><a href="#,Tx/a>Poland c/li>
> kl css <li><a href="#,Tx/a>Moldova </li>
> Bl images <lixa href="#,Tx/a>Germanv </li>
■ijs <lixa href="#,Tx/a> Spain </li>
v Bi templates <11 class-"share">
v Bl traveler <p>Our channel </p>
about.html
<a class-" she re-yt" h ref-"#"> Our channel </a>
ift base.html
</ll>
if^ index.html
</ul>
ft_init_.py
ft admin.py
</td>
& appspy <!-- end Sidebar -->
ft models.py <td vallgn=,,top" class="content">
ft tests.py < J - - breadcrumbs - - >
ft urls.py block breadcrumbs %}
ft viewspy {% endblock %}
v El travels 53
ft _init_.py
54 <!--Content block -->
ft a&gi-py
<div class="content-text">
ft settings.py
4% block content
ft urls.py
ft wsgi.py {% endblock %}

|3 db.sqliteJ </div>
ft manage.py 59 <! - - End of content block ->
Connecting the file styles.css
<link type="text/css" href="{% static
'traveler/css/styles.css' %}" rel = "stylesheet" />
Additionally, we connect an icon
<link rel = "shortcut icon" href="{% static
'traveler/images/main.ico' %}" type="image/x-icon"/>

Next comes the main menu block


{% block mainmenu %}

<div class="header">
<ul id = "mainmenu" class="mainmenu">
<li class="logo"><a href="#"><div
class="logo"></div></a></li>
{% for m in menu %}
{% if not forloop.last %}
<li><a href="#">{{m.title}}</a></li>
{% else %}
<li class="last"><a href="#">{{m.title}}</a>
</li>
{% endif %}
{% endfor %}
</ul>
<div class="clear"></div>
</div>
{% endblock mainmenu %}

Here we use a loop

{% for m in menu %}
{% if not forloop.last %}
<li><a href="#">{{m.title}}</a></li>
{% else %}
<li class="last"><a href="#">{{m.title}}</a>
</li>
{% endif %}
{% endfor %}

To iterate through our main menu, which we pass from


the views.py file and looks like..

menu = ["About the site", "Add an article",


"Feedback", "Log in"]

Here, only the names are being passed for now.


Next...
{% if not forloop.last %}
<li><a href="#">{{m.title}}</a></li>
{% else %}
<li class="last"><a href="#">{{m.title}}</a>
</li>
We display these names and pass a link. Instead of a
link, we currently have a placeholder '#'. We'll fill it in a
bit later.
Next comes the category selection menu

<table class="table-content" border="0"


cellpadding = "0" cellspacing = "0">
<tr>
<!--Sidebar cneBa -->
<td valign = "top" class="left-chapters">
<ul id = "leftchapters">
<li class="selected">All countries</li>
<li><a href="#"></a>Ukraine</li>
<li><a href="#"></a>Poland</li>
<li><a href="#"></a>Moldova</li>
<li><a href="#"></a>Germany</li>
<li><a href="#"></a>Spain</li>

Below is the content block

<!-- Content block -->


<div class="content-text">
{% block content %}
{% endblock %}
</div>
<!-- End of the content block -->

Displayed at the very bottom Footer


<!-- Footer -->
<tr><td valign = "top">
<div id="footer">
<p>traveler</p>
</div>
</td></tr></table>
<!-- End Footer -->

Next, based on this basic template, a base page


template is created. We change almost nothing, only
creating some styling.

{% extends 'traveler/base.html' %}
{% block content %}
<ul class="list-articles">
{% for p in posts %}
<li><h2>{{p.title}}</h2>
<p>{{p-content}}</p>
<div class="clear"></div>
<p class="link-read-post"><a href="#">Read the
post</a></p>
</li>
{% endfor %}
</ul>

{% endblock %}
We're not changing the about.html template for now.
Now let's see how the main page will look. Refresh the
page in your browser.

Ukraine Moldova

Moldova (Romanian: Republica Moldova), is a landlocked country in Eastern Europe.[17] It is bordered by Romania to the west and Ukraine to the north, east, and south.[18] The unrecognised Russian puppet state of Transnistria lies across
the Dniester on the country's eastern border with Ukraine. Moldova's capital and largest city1 is Chisinau.
Moldova

Germany

Germany
Germany (German: Deutschland, pronounced [ doYtflant] (listen)), officially the Federal Republic of Germany,[f] is a country in Central Europe. It is the second most populous country in Europe after Russia, and the most populous member
state of the European Union. Germany is situated between the Baltic and North seas to the north, and the Alps to the south; it covers an area of 357,022 square kilometres (137,847 sq mi), with a population of almost 84 million within its 16
Our channel constituent states. Germany borders Denmark to the north, Poland and the Czech Republic to the east, Austria and Switzerland to the south, and France, Luxembourg, Belgium, and the Netherlands to the west. The nation's capital and largest
city’ by population is Berlin and its financial centre is Frankfurt; the largest urban area is the Ruhr.
O YouTube

Ukraine
Ukraine (Ukrainian: ykpaiHa, romanized: Ukraina, pronounced [ukra jine] (listen)) is a country’ in Eastern Europe. It is the second-largest European country’ after Russia, which it borders to the east and northeast.[a][ll] Ukraine covers
approximately 600,000 square kilometres (230,000 sq mi).[b] Prior to the ongoing Russo-Ukrainian War, it was the eighth-most populous country in Europe, with a population of around 41 million people.[c][6] It is also bordered by Belarus
to the north; by Poland, Slovakia, and Hungary to the west; and by Romania and Moldovafd] to the southwest; with a coastline along the Black Sea and the Sea ofAzov to the south and southeast.[e] Kyiv is the nation's capital and largest
city’. The country’,s national language is Ukrainian, and most people are also fluent in Russian.[14]

Poland
Poland,[b] officially the Republic of Poland,[c] is a country’ in Central Europe. It is divided into 16 administrative provinces called voivodeships, covering an area of 312,696 km2 (120,733 sqmi). Poland has a population of over 38 million
and is the fifth-most populous member state of the European Union.[12] Warsaw is the nation's capital and largest metropolis. Other major cities include Krakow, Wroclaw, Lodz, Poznan, Gdansk, and Szczecin. Poland has a temperate
transitional climate. Its territory’ extends from the Baltic Sea in the north to the Sudeten and Carpathian Mountains in the south. The country is bordered by Lithuania and Russiato the northeast,[d] Belarus and Ukraine to the east, Slovakia
and the Czech Republic to the south, and Germany to the west. Poland also shares maritime boundaries with Denmark and Sweden.

Let's try to format the content output on the website in


such a way that the text below the header is not
displayed in full, as shown in the screenshot,
Moldova

Moldova (Romanian: Republica Moldova), is a landlocked country in Eastern Europe. [17] It is bordered by Romania to the west
and Ukraine to the north, east, and south. [ 18] The unrecognised Russian puppet state of Transnistria lies across the Dniester on
the country's eastern border with Ukraine. Moldova's capital and largest city is Chisinau.

Read post

Germany
Germany (German: Deutschland, pronounced [ dovtjlant] (listen)), officially the Federal Republic of Germany,[f] is a country in
Central Europe. It is the second most populous country in Europe after Russia, and the most populous member state of the
European Union. Germany is situated between the Baltic and North seas to the north, and the Alps to the south; it covers an area
of 357.022 square kilometres (137,847 sq mi), with a population of almost 84 million within its 16 constituent states. Germany
borders Denmark to the north, Poland and the Czech Republic to the east, Austria and Switzerland to the south, and France,
Luxembourg, Belgium, and the Netherlands to the west. The nation's capital and largest city by population is Berlin and its
financial centre is Frankfurt; the largest urban area is the Ruhr.
Read post

.and only a small fragment was displayed, for example,


the first 10 words. To achieve this in Django when
working with content, there are special filters. You can
view a list of all filters by following the link
https://django.fun/ru/docs/django/4.1/ref/templat
es/builtins/#ref-templates-builtins-filters
To do this, let's go to the index.html file and use the
filter...
truncatewords

Truncates a string after a certain number of words.

Argument: The number of words to truncate after, ango

For example:

{{ value|truncatewords:2 }}

If value is "Joel is a slug", then the output will be "Joel is

New lines in the string will be removed.

Let's change our code a little


{% extends 'traveler/base.html' %}
{% block content %}
<ul class="list-articles">
{% for p in posts %}
<li><h2>{{p.title}}</h2>
<p>{{p.content|truncatewords:10}}</p>
<div class="clear"></div>
<p class="link-read-post"><a href="#">Read the
post</a></p>
</li>
{% endfor %}
</ul>
{% endblock %}
[■] Project * O £. "r O styles,css x base.html x Lr* index.html

z M travels sources root, C:\Python\Django\travels {% extends 1 traveler/base .html’ %}


v M travels
> M media {% block content %}
> M static
k <ul class="list-ar'ticles"s
v El traveler
-{% for p in posts %}
v El migrations 6 i <lixh2>{-{p. title}}</h2>
ftOOOIJnitial.py
ft_init_.py < p>-{-{p. content | truncate wo rd s:lG}}</p>

v II static <div class="clear"x/div>

v M traveler < p class="link-read-post"xa href="#">Read oost</ax/p>

> M css IO </li>


> images ■ {% endfor %}
■i js
12 </ul>
Il templates
v II traveler
{% endblock %}
ifa about.html

And now let's see how this filter will affect the content
display. Let's refresh our page
As we can see, only a portion of the post is displayed.
We can briefly review the post, and if we need to delve
into the information, we click the 'Read the post'
button. For now, it doesn't lead to anything, as the
button is inactive.
Filters can be composite, meaning they can be applied
simultaneously to the same content. For example, let's
transform the string into all uppercase letters. In this
case, it doesn't make sense and is only required for
demonstration

Let's use the filter upper


<p>{{p.content|truncatewords:10|upper}}</p>
u project—’---------------------------------- =— styles.css-------- case,mmi-------------- inaex.mmi

travels sources root, C:\Python\Django\travels {% extends 'traveler/base.htmt' %}


v Bi travels
> M media {% block content %}
> M static
4 H<uL class="list-articles">
- Ea traveler
-{% for p in posts %}
v EB migrations
6 H <li><h2>-{-{p. titte}}</h2>
ij0001_initial.py
SMnit_.py <p>{{p.content 11runeatewo rds:IO|upper}}</p>

v M static <div ctass=,Tclear"x/div>

ta traveler <p class="link-read-post"xa href=,T#">Read post</ax/p>


10 i </li>
> kl css
> M images 11 ? endfon %}

ttjs 12 </ul>
v Bi templates
ta traveler
endblock %}
abouthtml
m base.html
_______________ irtrl w h<-ml____________________

Let's refresh the page


All countries

Ukraine Moldova
Poland MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA), IS A LANDLOCKED COUNTRY IN EASTERN ...

Moldova

Germany
Germany
Spain
GERMANY (GERMAN: DEUTSCHLAND, PRONOUNCED ['DOyTELANT] (LISTEN)), OFFICIALLY THE FEDERAL REPUBLIC

Our chaninel

O YouTube Ukraine
UKRAINE (UKRAINIAN: UKRAINE, ROMANIZED: UKRAINA, PRONOUNCED [UKRVJINV] (LISTEN)) IS A ...

In conclusion of this topic, I want to highlight one


important feature. Let's go to the admin panel at
http://127.0.0.1:8000/admin/
and wrap a fragment of content in the <h3></h3>
tag. Then, save it and go back to the main page

Change All countries


Moldova

Moldova
Title:

Articta tad: <h3>Moldova (Romanian: Republica Moldova)</h3>, is a landlocked country in Eastern Europe.[17] It is
bordered by Romania to the west and Ukraine to the north, east, and south. [18] The unrecognised Russian
puppet state of Transnistria lies across the Dniester on the country's eastern border with Ukraine.
Moldova's capital and largest city is Chisinau.

At the moment: photo&/2022/10/13/M use urn of Hretory.jpg


Photo:
Change: Choose File "| File not selected

Q Is puplished
About tb-e .site Add Article Feedback

All countries

Ukraine Moldova

Poland <H3>MO LDOVA (ROMANIAN: REPUBLICA MOLDOVA)</H3>, IS A LANDLOCKED COUNTRY IN EASTERN ...

Moldova

Germany
Germany
Spain
GERMANY (GERMAN: DEUTSCHLAND, PRONOUNCED [ DOyTZLANT] (LISTEN)), OFFICIALLY THE FEDERAL REPUBLIC ..

Our channel

As we can see, the <h3></h3> tag is displayed as


text. If we view the HTML page's source code, we'll see
the following

67 <ui ciass="iist-articies >


68
69 <ll> <h2>Moldova</h2>

70 <p>&ltjHB&gt;MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA)&lt;/HB&gt;, IS A LANDLOCKED COUNTRY IN EASTERN ...</p>


71 <div class="clear"></div>
72 <p class-’link-read-posfxa href="#">read post</a></p>

74
75 <lixh2>Germany</h2>
76 <p>GERMANY (GERMAN: DEUTSCHLAND, PRONOUNCED ['DDYTILANT] (LISTEN)), OFFICIALLY THE FEDERAL REPUBLIC ...</p>
77 <div class="clear,’x/div>
79 <p class=,,link-read-post"><a href="#">read post</a></p>
79 </li>

<p>&lt;H3&gt;MOLDOVA (ROMANIAN: REPUBLICA


MOLDOVA)&lt;/H3&gt; -

This is called tag escaping. Django does this


intentionally! If any tags are inserted as content, they
won't execute. This includes the script tag, which can
contain malicious code and create an extra vulnerability
for the website. To prevent this, Django escapes such
tags.
Nevertheless, there are times when there's a need for
tags to function as tags and not be escaped. To achieve
this in Django, there is a tag called

{% autoescape <on|off> %} KOHTeHT{%


endautoescape %}
On - All tags are escaped
Off - Escaping is disabled.

Let's apply it to our template.


<li><h2>{{p.title}}</h2>
{% autoescape off %}
<p>{{p.content|truncatewords:10|upper}}</p>
{% endautoescape %}
<div class="clear"></div>

v to travels sources root, C:\Python\Django\travels {% extends 'traveler/base.html' %}


v to travels
> to media {% block content %}
> Bi static
<ul class-"list-articles,r>
v El traveler
- {% for p in posts %}
v Bi migrations
< lixh2>{{p. title}}</h2>
r^, OOOIJnitial.py
- {% autoescape off %}
ft_init_.py
v to static <P>{{p- content|truncatewords :161upper}}</p>

v to traveler -{% endautoescape %}

> to css <div class-"clear'rx/div>


> to images <p class="link-read-post"xa href-"#">Readpost</ax/p>
tojs </li>
v to templates endfor %}
v to traveler
</ul>
n abouthtml
[fj base.html
{% endblock %}
ifa index.html
___________ rH init .py_______________________

Let's refresh our page


About the site Add Article Feedback

AU countries

Ukraine Moldova
Poland MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA)

Moldova . IS A LANDLOCKED COUNTRY IN EASTERN ...

Germany

Spain
Germany
GERMANY (GERMAN: DEUTSCHLAND. PRONOUNCED [ DOyTILANT] (LIST
Our channel REPUBLIC ...

Generating URL addresses in


templates.
Previously, in almost all links, we used to create a
placeholder like this:
Let's create full-fledged links to pages. To do this in
Django, you can use a special tag:
{% url '<URL address or route name>' [link
parameters] %}
[link parameters] - an optional parameter.
So, let's define a route to the main page. Go to your
project, open the base.html template file.
The first thing you can do is to add a slash to the line
like this:

<li class="logo"><a href="/"><div class="logo" >


</div></a></li>

</head>
B<body>
Retable class="table-page" border=0 cellpadding="G" cellspacing="Q">
Hctrxtd vallgn=top>
{% block mainmenu %}

<div class="header">
<ul id="mainmenu" class="mainmenu">
18 * <li class="logo"xa href="/"xdiv class="logo" ></divx/ax/li>
_______ {% for m in menu %}___________________________________________________

And this will work. If you start our server and click on
the logo (currently represented by an empty square,
which we'll fix later), you'll be taken to the same main
page.
Creating links in this way is not the best solution
because the URL of the main page doesn't necessarily
have to match the name of the main page. If it
changes, you'd have to make changes in all the
templates. It's more practical to use route names.
If you open the urls.py file, you'll see the names of our
routes

urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
]
Cewnac Hac uHTepecyem uma home.
Let's change the previous line to:
<li class="logo"><a href="{% url 'home' %}"><div
class="logo" ></div></a></li>

n<trxtd valign=top>
{% block mainmenu %}

<div class="header">
<ul id="mainmenu" class="mainnenu">
18 <11 class="logo"xa href="{% url 'home' %}-"xdiv class = "logo" x/divx/ax/li>
{% for m in nenu %}
20__________ <% If not forloon.last ________________________________________________________

We'll save this, go to the browser, refresh the page, and


see that the link to the main page is indeed preserved.
Next, what we'll do is add links to menu items on the
website.

To do this, in the project where the menu item is


created, open the views.py file, and replace the line...

from .models import *

menu = ["About", " Add article " f "Feedback" f "To come in"]

on
menu = [{'title': 'About the site', 'url_name': 'about'},
{'title': 'Add an article', 'url_name': 'add_page'},
{'title': 'Contact us', 'url_name': 'contact'},
{'title': 'Login', 'url_name': 'login'}
]
menu = [{'title': "About", 'url_name': 'about'},
{'title': "Add article", 'url_name': 'add_page'},
{'title': "Feedback", 'url.name': 'contact'},
{'title': "Tocomein", 'url_name': 'login'}
]

So, we replaced the old list with a new list of


dictionaries, each containing two keys: 'title' and
'url_name'.
Next, let's go to the urls.py file and add three more
routes
urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
path('addpage/', addpage, name='add_page'),
path('contact/', addpage, name='contact'),
path('login/', login, name='login'),
]

Let's add three more view functions, initially as


placeholder functions. We'll go back to views.py and
remove the categories and archive functions, replacing
them with:
def addpage(request):
return HttpResponse("Adding an article")

def contact(request):
return HttpResponse("Feedback")

def login(request):
return HttpResponse("Authorization")

to traveler
radef index(request) :
v to migrations
posts = Dir_tra vet. objects. alL()
&0001.jnitial.py
ft_init_.py return render (request, 'traveler/index.html', {'posts': posts, 'menu': menu, 'title': ' Mainpage'})

v to traveler ndef about(request):


h return render (request, 'traveler/about.html', {'menu': menu, 'title': 'About the site1})
> Bi images
Hdef addpage(request):
v to templates A return HttpResponse( "Adding an article")
v to traveler
[m about.html
def contact(request):
[m base.html
B return HttpResponse("Feedback")
im index.html
ft _init_.py
ft admin.py Hdef login(request):
ft apps.py M return HttpResponse("Authorization")

Let's make some slight modifications to the index


function right away. If we take a closer look at this
function...

def index(request):
posts = Dir_travel.objects.all()
return render(request, 'traveler/index.html',
{'posts': posts, 'menu': menu, 'title': ' Home page})
Intuitively, we might want to shorten the bottom line of
the index function ourselves. This is a commendable
desire since shorter lines are more readable and
generally easier to understand. Let's create a special
dictionary within this function where we list all the
parameters we'll be passing.

def index(request):
posts = Dir_travel.objects.all()
context = {
'posts': posts,
'menu': menu,
'title': 'Home page'
}
return render(request, 'traveler/index.html',
context=context)

■ travels sources root, C:\Python\Django\travels ’('title': "Feedback", 'url_name': 'contact'},


v ■travels -[’title': "to come in", 'url_name': 'login'}
> ■ media I 10 H]
> ■ static
v [a traveler
12 def index(request):
v El migrations
posts = Dir_travel.objects.all()
& 0001Jnitial.py
14 * context = {
|£_init_.py
v ■ static 'posts': posts,
v ■ traveler 'menu': menu,
> ■ css 17 'title': 'homepage'
18 i }
> ■ images
■ js 19 i- return render(request, 'traveler/index.html', context=context)
v ■ templates
v ■traveler
21 Hdef about(request):
im about.html
22 i-i return render (request, ' traveler/about. html', {'menu': menu, 'title': ' Asoutthe site'})
m base.html
m index.html
___________ l-~~ addpaqe(reciuest):___________________________________________________________________________

Now, let's incorporate our modified menu into the


base.html file. Let's focus on our menu block, and
instead of placeholders, include the URLs.

{% block mainmenu %}

<div class="header">
<ul id = "mainmenu" class="mainmenu">
<li class="logo"><a href="{% url 'home' %}">
<div class="logo" ></div></a></li>
{% for m in menu %}
{% if not forloop.last %}
<li><a href="{% url m.url_name %}">{{m.title}}
</a></li>
{% else %}
<li class="last"><a href="{% url m.url_name %}">
{{m.title}}</a></li>
{% endif %}
{% endfor %}
</ul>
<div class="clear"></div>
</div>
{% endblock mainmenu %}

</head>
0<body>
<table class="table-page" border=0 cellpaddlng="0" cellspacing="G">
Actrxtd valign=top>
{% block mainmenu %}

<div class="header">
cut id="mainmenu" class="mainmenu">
• <11 class="logo"xa href="{% url 'home' %}"xdiv class="logo" x/divx/ax/li>
19 {% for m in menu %}
{% if not forloop.last %}
<lixa href="{% url m.url_name %}',>{{m.title}}</ax/li>
22 {% else %}
<li class="last"xa href="{% url m.url_name %}">{-{m.title}}</ax/li>
endif
{% endfor
26 </ul>
<div class="clear"x/div>
</div>
endblock mainmenu %}

H<table class="table-content" border="0" cellpadding="0" cellspacing="G">


<tr>

So, in each iteration of the loop, we take the "m" - an


element from the menu dictionary, extract its
url_name. Then, this name is retrieved from urls.py
within the urlpatterns collection and is used to
substitute the specific route.
Let's return to the browser, refresh the main page, and
click on "About the site" to navigate to the page
shown below in the screenshot

All countries

Ukraine About the site

Poland Page content

Moldova

Germany

Spain

Our channel

O YouTube

flanee, HaMMew “flo6aBUTb cmamtw”

<- -> O G> 127.0.0.1:8000/add page/

Adding an article

We remember that we created placeholder functions.


We'll add more functionality later!
The same approach applies to " Feedback " and " To
come in." Now, we need to define links for our list of
articles and understand how to create dynamic links at
the template level.
Let's open the urls.py file and define another route.

urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
path('addpage/', addpage, name='add_page'),
path('contact/', addpage, name='contact'),
path('login/', login, name='login'),
path('post/<int:post_the>/', show_post,
name='post'),
]

We have a specific article that will open through such a


route
path('post/<int:post_the>/', show_post, name='post'),
int:post_the - The identifier of the specific route
name='post' - The name of the route
Typically, in real projects, instead of 'post_the,' a
string, also known as a slug, is used. The slug is written
in Latin characters and reflects the context of the
article. Such links are better ranked by search engines
and are more understandable for users. We will fix this
a bit later. Right now, our task is to figure out how to
generate dynamic URL links at the template level.
Next, we will define the 'show_post' function.
Let's open the 'views.py' file.
We'll define it as a placeholder function. We'll format it
later.

def show_post(request, post_the):


return HttpResponse(f" Displaying the article with
id = {post_the}")

v to templates
def togin(request):
v to traveler
return Http Re spun se(" Authorization ")
go abouthtml
go base.html
def show_post(request, post_the):
m index,html
S_init_.py = return HttpRe span se(f' Display article with id = {post_the}")

We just need to properly set up these links in


index.html. There are two ways to do this. Let's go
through the first one.

<p class="link-read-post"><a href="{% url 'post' p.pk


%}"> Read the post </a></p>
✓ to travels sources root, C:\Python\Django\travels {% extends 'traveler/base.html' %}
v to travels
> to media
{% block content %}
> to static
Kul class="list-apticles">
V Ea traveler
- {% for p in posts %}-
v to migrations
I <lixh2>{{p. title}}</h2>
0001JnitiaLpy
ft_init_.py - {% autoescape off %}

v to static < p>{{p.content|truncatewords:10|upper}}</p>


v to traveler - [% endautoescape %}
> to css <div class="clear"x/div>
> to images < p class="link-read-post',xa href="{% urt 'post' p.pk %}">Readoost</ax/p>
tojs i </li>
v to templates
- {% endfor %}
v to traveler
i</ul>
about.html
ifX base.html
{% endblock %}
a index.html

post - The name of our link


p.pk - (pk) - The identifier of our record; (p) - an
instance of our model Dir_travel, that has an attribute
pk, which Django uses by default as the record
identifier.
Meaning the 'url' tag will take the address using the
name 'post' and instead of 'post_the,' it will substitute
this record identifier, forming the corresponding link.
Let's update the page and see how it all looks. Click on
the 'Read the post' button. If everything was done
correctly and without errors, you will get the page
<- -> C (D 127.0.0.1:8000/post/5/

Disolav article with id = 5

Let's experiment with the other posts.


I<- -> C 0 127.0.0.1:8000/post/4/
Disolav article with id = 4

I<- -> C © 127.0.0.1:8000/post/2/


Disolav article with id =2

But there's another way. The method described above


has one drawback. If the format of our address changes
from 'post_the' to, for example, using a slug, we will
have to change the 'p.pk' parameter in the 'index.html'
file. In this case, we'd need to pass not the identifier
but the slug, which is not very convenient. We may
have dynamic addresses in various places and
templates, and it's easy to forget to update something.
Let's use the second method. We can define a method
in the class of our model in the 'models.py' file that
will generate the desired route for us.

from django.db import models


from django.urls import reverse

class Dir_travel(models.Model):
title = models.CharField(max_length=255, verbose_name
= " Heading ")
content = models.TextField(blank=True, verbose_name =
" Article text ")
photo =
models.ImageField(upload_to="photos/%Y/%m/%d/",
verbose_name = " Photo ")
time_create =
models.DateTimeField(auto_now_add=True, verbose_name
= " Time of creation ")
time_update = models.DateTimeField(auto_now=True,
verbose_name = " Change time ")
is_puplished = models.BooleanField(default=True)

def_ str_ (self):


return self.title

def get_absolute_url(self):
return reverse('post', kwargs={'post_the':
self.pk})

We have defined a method in this class

def get_absolute_url(self):
return reverse('post', kwargs={'post_the':
self.pk})

that will generate the necessary route to a specific


record for us.
The 'self' reference is a reference to an instance of the
'Dir_travel' class. Consequently, with this reference,
we can access any attribute of the current record. In
this case, we are accessing the 'pk' attribute and, using
the 'reverse' function (which needs to be imported),
we will generate a route with the name 'post'
according to the template specified in the 'urls.py' file

path('post/<int:post_the>/', show_post, name='post'),


To do this, we additionally pass the 'post_the'
parameter with the identifier of the current record,
which is 'self.pk.' This identifier will be inserted into
the template
path('post/<int:post_the>/', show_post, name='post'),
and the necessary link will be generated, which will be
returned by the function
def get_absolute_url(self):

Next, you need to change the line in the 'index.html'


file
<p class="link-read-post"><a href="{%
p.get_absolute_url %}"> Read post </a></p>

Just keep in mind that you should write the function


without parentheses, as we won't be calling it, and the
template engine will take care of that.

{% extends 'traveler/base.html' %}

{% block content %}
H<ul class="list-articles">
{% for p in posts %}
<lixh2>{{p. title}-}</h2>
{% autoescape off %}
<p>{{p•content|truncatewords:10|upper}}</p>
{% endautoescape %}
<div class="clear"x/div>
11 <p class="link-read-post"xa href="{% p. get_absolute_url %}">Readpost </ax/p>
</li>
{% endfor %}
</ul>

{% endbtock %}•

A
Let's go back to the browser, refresh the page.
«- -> C © 127.0.0.1:8000

TemplateSyntaxError at I
Invalid block tag on line 11 'p.get_absolute_url'; expected 'empty' or 'endfor'. Did you forget to register or load this tag?
Request Method: GET
Request URL: http://127.001:8000/
Django Version: 4.1.1
Exception Type: TemplateSyntaxError
Exception Value: Invalid block tag on line 11: 'p.get_absolute_url', expected 'empty' or 'endfor'. Did you forget to register or load this tag?
Exception Location: C:\Python\Django\travels\venv\lib\site-packages\django\template\base.py, line 558, in invalid_block_tag
Raised during: traveier.views. index
Python Executable: C:\PythDn\Django\travels\venv\Scripts\python exe
Python Version: 3.10.7
Python Path: [ ,C:\\PythorA\DjangcA\travels ’x\tr'avels,J
'C:\\Python\\python310.zip',
■C:\\Python\\DLLs',
■CiWPythonWlib'j
'C:\\Python',
'C: WPython V\Django\.\travels\\venv',
'C:\\Python\\Django\\travels\\venv\Mib\\site-packages']
Server time: Mon, 31 Oct 2022 11:37:26 +0000

Error during template rendering


In template C:\Python\Django\travels\travels\traveler\templates\traveler\index.html. error at line 11

Invalid block tag on line 11: 'p.get_absolute_url', expected 'empty' or'endfor'. Did you forget to register or load this tag?
1 {% extends ’traveler/base.html’ %}
2
3 {% block content
4 <ul class-"list-articles’^
5 {% for p in posts %}
6 <lixh2>{{p. title}}</h2>
{% autoescape off %}
8 <p>{{p.content|truncatewords:10|upper}}</p>
9 {% endautoescape %}
10 <div class="clear'’x/div>
11 <p class="link-read-post"xa href="{% p.get_absolute_url %}">Read post</ax/p>
12 </li>
13 endfor %}
14 </ul>
15
16 {% endblock 56}
17

And we see that an exception has occurred. We made a


mistake in the line

<p class="link-read-post"><a href="{%


p.get_absolute_url %}"> Read post </a></p>

The point is that you need to call it not as a tag, but as


a variable.
We'll fix it

<p class="link-read-post"><a href="{{


p.get_absolute_url }}"> Read post </a></p>
Now everything should work. This approach is
considered preferable, but only when our links are
associated with records in the database.

Creating relationships between data


models

Let's add another table for categories and connect it


with the posts table in a way that each article is
associated with the relevant category. For example, the
category "Country" - "Poland" corresponds to the
"City" - "Warsaw".
Dividing data into multiple tables and establishing
relationships between them is called data
normalization, and it's a principle worth following.
So, let's create another table, a model with categories,
and connect it to the posts table.
Add another field to the Dir_travel table, cat_id:
Integer, a foreign key that will be defined as a foreign
key, storing the category identifier.
In the categories table, define two fields

Id: Integer, primary key


name: Varchar
The Django framework has three special classes for
organizing relationships.
ForeignKey - For Many-to-One relationships
(relationship fields).
ManyToManyField - For Many-to-Many relationships
(many-to-many fields).
OneToOneField - For One-to-One relationships (one-
to-one fields).

In this case, ForeignKey is suitable for us, as we have


multiple posts associated with a specific category.
ManyToManyField is suitable, for example, for
defining tags.
To use the ForeignKey class, we need to specify two
mandatory arguments:
ForeignKey(<reference to the primary model>,
on_delete=<deletion behavior>)
Our new model, category, is the primary model, and
the Dir_travel model is the secondary one.
on_delete is the second parameter that determines
the behavior when a record is deleted from the primary
model.
The cat_id key should reference a category. If, for some
reason, a category is deleted, we need to decide what
to do with the records that reference that category.
There are several options.
-models.CASCADE - When deleting a record from the
primary model (Category), all records from the
secondary model (Dir_travel) linked to the deleted
category are also removed.
-models.PROTECT - Prohibits the deletion of a record
from the primary model if it is being referenced in the
secondary model (throws an exception).
-models.SET_NULL - When deleting a record from the
primary model, sets the foreign key value to NULL for
the corresponding records in the secondary model.
- models.DEFAULT - When deleting a record from the
primary model, sets the foreign key value to the default
value defined in the ForeignKey class instead of
NULL.
- models.SET()- The same thing, but sets a custom
value.
- models.DO_NOTHING - Deletion of records in the
primary model does not trigger any actions in the
secondary models.

More detailed information about relationships in


Django can be found
https://django.fun/ru/docs/django/4.1/topics/db/models/
#many-to-one-relationships

Let's look at a specific example of how the ForeignKey


class is used to establish 'Many-to-One' relationships,
as described above. To do this, let's navigate to our
project, open the models.py file where we define the
interactions of all models, and add another model
named Category. It will inherit from the same base
class, and we'll define a field 'name' for the category's
name. There will also be another field 'id,' which
Django automatically generates.

class Category(models.Model):
name = models.CharField(max_length=100,
db_index=True)

def_ str_ (self):


return self.name
max_length=100 - Maximum length of 100 characters
db_index=True - The field will be indexed, meaning that
searches on this field will be faster.

def_ str_ (self):


return self.name
14 Of def __str. (self):
return self.title
16
def get_absolute_url(self):
return reverse('post1, kwargs={'post_the': self.pk})
19

21
class Meta:
verbose_name = " all countries "

verbose_name_plural = " all countries "

ordering = ['-time_create ', 'title']

27 class Category(models.Model):
name = models.CharField(max_length=10O, db_ index=True)

3G of def (self):
I
return self.name

This method returns the name of the category. Next, in


the secondary model, we will add a foreign key.

cat = models.ForeignKey('Category',
on_delete=models.PROTECT)
'Category' - We define the association with the
category through a string.
on_delete=models.Protect - We prohibit deleting
categories from the Dir_travel model if there are
references. We create/modify this table in our database.
□class Oir_travel(models.Model):
title = models.ChanField(max_leng1 =255, verbose_name = "header")
content = models.TextField(blank=True, verbose_name = " Article text")
photo = models. ImageField(upload_1 ="photos/%Y/%m/%d/", verbose_name = "Photo")
time_create = models. DateTimeField (auto_now_add=True, verbose_name = "Time of creation")
time_update = models.DateTimeField(ajto_no.'.=Tnue, venbose_name = "Changetime 11)
is_puplished = models. BooleanField(defaiilt=True)
cat = models.ForeignKey(1 Category’, on_delet =models.PROTECT)

15 Of def (self):
16 return self.title
17
def get_absolute_url(self):
return reverse(1 post1 , kwai gs={'post_the 1: self.pk})

21

23 class Meta:
verbose_name = " All countries"
25 verbose_name_plural = "All countries"
26 ordering = [1-time_create', ’title1]

We modify the first table, Dir_travel, and create the


second table, Category, accordingly. To do this, we
need to generate a new migration.
Python manage.py makemigrations
And... encountered an error.

(venv) PS C:\Python\Cjango\travels\travels> python manage.py makemigrations


It is impossible to add a non-nullable field 'oaf to dir_travel without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on alt existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.

The issue seems to be that when modifying the first


table, Dir_travel, the 'cat' field references the
Category table, which hasn't been created yet.
Temporarily, let's define this field as
cat = models.ForeignKey('Category',
on_delete=models.PROTECT, null=True),
meaning that null values are allowed during creation.
Press '2', exit, and repeat the migration.
H Project ▼ Q 2. -r — styles.css J base.html

v Bl travels sources root, C:\Python\Django\travels


v M travels
> M media
Hclass Dir_travel(m
> M static
title = models
- Ea traveler
content = mode
v migrations
photo = models
i^OOOIJnitial.py
i^0002_category_alter_dir_travel_options_and_more.py time_create =
£_init_.py time_update =
v M static is_puplished =
traveler cat = models.F
> tacss
> M images 15 ©T def __str__(se
tajs
16 return sei
v M templates
traveler
def aet absolu
about.html Dir_travel > _str_O

Terminal: Local

2) Quit and manually define a default value in models.py.


Select an option: 2
(venv) PS C:\Python\Django\travels\travels> python manage.py makemigrations
Migrations for 'traveler':
traveler\migrations\0G02_category_alter_dir_travel_options_and_more.py
- Create model Category
- Change Meta options on dir_travel
- Alter field content on dir_travel
- Alter field photo on dir_travel
- Alter field time_create on dir_travel
- Alter field time_update on dir_travel
- Alter field title on dir_travel
- Add field cat to dir_travel
(venv) PS C:\Python\Django\travels\travels> F

We can see that another migration file has appeared in


the folder. Now, let's execute these migrations by
running the command:
Python manage.py migrate
(venv) PS C:\Python\Django\travels\travels> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, traveler
Running migrations:
Applying traveler.0002_category_alter_dir_travel_options_and_more. .. OK
(venv) PS C:\Python\Django\travels\travels> F

So, all tables in the database have been created. It is


advisable to plan all relationships in advance during the
design phase and avoid making changes to existing
database tables later on. Let's add two category tables.
Go to the Django console by running the command:
python manage.py shell

Let's work with Django's ORM in the console to better


understand the advantages of using this system. In this
shell, we will first import our models
from traveler.models import *
and then proceed
Category.objects.create(name=' Europe ')

Using the Category model, we access the records


manager 'objects' and call the 'create' method. With
this method, we create a new entry in the Category
table, and the category will be named 'Europe.'

Let's create additional categories

Category.objects.create(name=' Asia')
Category.objects.create(name=' Africa')
Category.objects.create(name=' North America’)
Category.objects.create(name=' South America')

Then, in the table Dir_travel, we will set the value in


the id field to 1, meaning that all these records will
correspond to 1.
w_list = Dir_travel.objects.all()
Let's select all records from the Dir_travel table
w_list - A variable that refers to the list of all records in
this table.
Next, we will use the update operation to change the
value of the cat_id field to 1.
w_list.update(cat_id=1)

oj >» w_list. update (cat_id=l)


f 4
L_

We see that changes have been made for four fields.

So, we have created the Category table and


established a relationship between the tables.
Now let's display the list of categories in the base.html
template. Open the base.html file, find the list of
categories in the left sidebar, and make some changes
according to the logic of our site, as there was a
mismatch.
Let's assume that in the content block, information
about a specific country will be displayed, which will be
in its category indicating its continent affiliation. In the
left sidebar, we will list the categories to which
countries belonging to a particular continent will be
included.
Instead of countries located in Europe, we will specify
the names of continents.

<!--Sidebar cneBa -->


<td valign="top" class="left-chapters">
<ul id="leftchapters">
<li class="selected"> Continents </li>
<li><a href="#"></a> Europe </li>
<li><a href="#"></a> Asia </li>
<li><a href="#"></a> Africa </li>
<li><a href="#"></a> North America </li>
<li><a href="#"></a> South America </li>
<li class="share">
<p> Our channel </p>
<a class="share-yt" href="#"></a>
</li>
</ul>
</td>
<!-- End Sidebar -->
ti n Project ■» © £. -r O ~ mi styles.css x base.html views,py '■< urls.py x index.html
o’
<table class-^table-content" border-"0" cellpadding
v M travels sources root C:\Python\Django\travels
v M travels <tr>

> M media <!--Sidebar cneaa -->


> M static <td valign=,’top" class="left-chapters">
El traveler <ul id="leftchapters'r>
v £■ migrations <li class=,,selected">Continents </li>
ftOOOIJnitial.py
<li><a href="#"></a> Europe </li>
0002_categ ory_a lter_d ir_travel_o pti on s_a nd_more,py
<li><a href=,T#"x/a> Asia </li>
ft_init_.py
<li><a href="#"></a>Africa </li>
v M static
<lixa href=,T#"x/a>NorthAmerica </li>
v M traveler
<lixa h ref="#" x/a>South America </li>
> M css
> Bi images <11 class=nshare">

Mjs <p>Our channel </p>


v M templates <a class="share-yt" href="#"x/a>
v Bi traveler </li>
|7^ about.html
html > body > table.table-page > tr > td > table,table-content > tr > td.le-

Continents

Evpona Moldova
Asia MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA)

Africa

North America

South America

Our channel

Q YouTube

Now we will continue to change this block. Instead of


the lines in the comment <!--Left Sidebar -->, write

<!--Sidebar left -->


<td valign="top" class="left-chapters">
<ul id = "leftchapters">
{% if cat_selected == 0 %}
<li class="selected"> Continents </li>
{% else %}
<li><a href="{% url 'home' %}"> Continents
</a>></li>
{% endif %}

{% for c in cats %}
{% if c.pk == cat_celected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li><a href="{{ c.get_absolute_url }}">
{{c.name}}</a></li>
{% endif %}
{% endfor %}

<li class="share">
<p> Our channel </p>
<a class="share-yt" href="#"></a>
</li>
</ul>
</td>
<!--KoHe^ Sidebar -->
Q Project © hi styles,css < 1^ base.html p^views.py < urls.py x indexhtml x j£, models,py x

M travels sources root, C:\Python\Django\travels <ul id="Leftchapters"?


v M travels
- {% if cat_setected == 0 %}
> IM media
di cLass="selected"?Continents </Li?
> M static
- [% else %}
v El traveler
dixa href=,T{% url 'home' %}">Continents</a»</li>
v El migrations
- {% endif %}
00-01 Jnitial.py
[§i0002_category_alter_dir_travel_options_and_more.py
S-init-Py - {% for c in cats %}
v M static {% if c.pk == cat_celected %}
v M traveler di class=" selected "?{{c. name}]-</li?
> M css {% else %}
> M images dixa href="H c.get_absolute_url }}"?{{c.name}}</ax/li>
Mjs
{% endif %}
M templates
- {% endfor %}
v M traveler

about.html
di class="share"?
base.html
im indexhtml
<p?Haiu K3Haji</p>
_init_.py <a class="share-yt" href="#"x/a>
admin.py
S appspy i </ul>
models,py i </td>
S tests,py
<!--Koh6M Sidebar -->
£ urls.py
i<td valign="top1' class="content"?
Let's go through this code.
Line:
<li class="selected"> Continents </li>
will be displayed always. And if
{% if cat_selected == 0 %}
we display this line not as a link but as text. If
cat_selected is not equal to "0", then we display it as
a link to the main page
<li><a href="{% url 'home' %}"> Continents </a>>
</li>.
All subsequent categories will be formed using a loop
{% for c in cats %}
{% if c.pk == cat_celected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li><a href="{{ c.get_absolute_url }}">
{{c.name}}</a></li>
{% endif %}
{% endfor %}
We will pass the collection "cats" (this collection will
consist of objects of the Category class) and iterate
through it. If in this loop the primary key "c.pk" is equal
to "cat_selected"
{% if c.pk == cat_celected %}
then we output the current category not as a link but as
text
<li class="selected">{{c.name}}</li>
Otherwise, it will be displayed as a regular link.
<li><a href="{{ c.get_absolute_url }}">{{c.name}}
</a></li>
In other words, we display it by name, and we will form
the link using the method
get_absolute_url
To use this method, you need to add it to our Category
model.

class Category(models.Model):
name = models.CharField(max_length=100,
db_index=True)

def_ str_ (self):


return self.name

def get_absolute_url(self):
return reverse('category', kwargs={'cat_the':
self.pk})

Now, the route with the name "category" needs to be


specified in the urls.py file

urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
path('addpage/', addpage, name='add_page'),
path('contact/', addpage, name='contact'),
path('login/', login, name='login'),
path('post/<int:post_the>/', show_post, name='post'),
path('category/<int:cat_the>/', show_category,
name='category'),
]
Let's define the function show_category in the
views.py file, and for now, it will look like a placeholder
function.

def show_category(request, cat_the):


return HttpResponse(f" Displaying the category c
id = {cat_the}")

And finally! For base.html to work, it needs to be


passed the collection "cats" and the variable
"cat_selected".

Let's go to the views.py file and find the index


function

def index(request):
posts = Dir_travel.objects.all()
cats = Category.objects.all()
context = {
'posts': posts,
'cats': cats,
'menu': menu,
'title': 'Home page’,
'cat_selected': 0,
def index(request):
posts = Dir_travel.objects.al1()
cats = Category. objects. attO
context = {
1 posts': posts,
'cats': cats,
'menu': menu,
'title': ' Main page >
'cat_selected ' : 0,
}
return render(request, 'traveler/index.html', context=context)

Let's go to the views.py file, find the index function,


and additionally read entries from the "cats" table
using the command:
cats = Category.objects.all()
Pass this collection and the variable 'cat_selected'
with the value 0 to the template, so that all entries are
displayed on the main page.
Now let's see how it will work. Go to the terminal and
type the familiar command
Python manage.py runserver
And let's go to the link http://127.0.0.1:8000/
Continents

Evpona
Moldova
Asia
MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA)
Africa
, IS A LANDLOCKED COUNTRY IN EASTERN ...

North America Read post

South America
Germany
Our channel GERMANY (GERMAN: DEUTSCHLAND, PRONOUNCED [DOyTLLANT] (LISTEN)), OFFICIALLY THE FEDERAL REPUBLIC ...

fl YouTube

Ukraine
UKRAINE (UKRAINIAN: YKPAIHA. ROMANIZED: UKRAINA, PRONOUNCED [UKRV JINV] (LISTEN)) IS A ...

Read post

Poland

We see all our categories on the left side. If you click on


each link one by one, each of them will work correctly,
displaying our placeholder pages with the specified id.
C 0 127.0.0.1:8000/category/2/

Category display =2

Next, let's make different articles display in separate


categories. To do this, open the views.py file and
modify the show_category function. Instead of the
placeholder, write the same code as in the index
function. We also select posts, but not all of them, only
those that correspond to the current category, i.e.,
where the foreign key cat_the matches the key we
passed in the request.

def show_category(request, cat_the):


posts = Dir_travel.objects.filter(cat_the=cat_the)
cats = Category.objects.all()
context = {
'posts': posts,
'cats': cats,
'menu': menu,
'title': ' Display by Categories ',
'cat_selected': cat_the,
}
return render(request, 'traveler/index.html',
context=context)

Adef show_category(request, cat_the):


posts = Dir_travel.objects.filter(cat_the=cat_the)
cats = Category.objects.all()
context = -{
1 posts 1: posts,
'cats': cats,
'menu': menu,
® 'title': ' Display by category ' f
'cat_selected': cat_the,
T ?
return render(request, 'traveler/index.html', context=context)

cats = Category.objects.all() - We select all the


categories that we want to display on the page.
With the help of the context, we pass the same data.
Only 'title': 'Display by Categories' and
'cat_selected': cat_the are changed. Now let's check
how it will work. Start the server and refresh the page.
Afterward, let's navigate to the category, for example,
'Europe'.

Field Error at /category/1/


Cannot resolve keyword 'catthe' into field. Choices are: cat, catjd, content, id, ispuplished, photo, time create, time update, title

We encountered an error. What could be the issue?!


Firstly, we made a mistake in the base.html file, in
line 37
{% if cat_selected == 0 %}
<li class="selected"> Continents </li>
{% else %}

Next, let's go to the file models.py


In line 13, models Dir_travel
cat = models.ForeignKey('Category',
on_delete=models.PROTECT, null=True)
Django automatically adds 'id' to the 'cat' field with an
underscore. Accordingly, let's open urls.py and make
the correction

urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
path('addpage/', addpage, name='add_page'),
path('contact/', addpage, name='contact'),
path('login/', login, name='login'),
path('post/<int:post_the>/', show_post, name='post'),
path('category/<int:cat_id>/', show_category,
name='category'),
]

Next, in the views.py file, let's change 'cat_the' to


'cat_id'

def show_category(request, cat_id):


posts = Dir_travel.objects.filter(cat_id=cat_id)
cats = Category.objects.all()
context = {
'posts': posts,
'cats': cats,
'menu': menu,
'title': ' Display by Categories ',
'cat_selected': cat_id,
}
return render(request, 'traveler/index.html',
context=context)

Restart the server, refresh the page. When clicking on


the 'Europe' tab, all articles related to Europe will be
displayed. Clicking on other tabs will show nothing
since the lists are empty. Additionally, when clicking on
the 'Europe' tab, it is displayed not as a link but as
regular text, while other tabs appear as links. All this is
possible thanks to the 'cat_selected' variable. When
checking in the base.html template
{% if cat_selected == 0 %},
the 'Continents' tab is displayed as text, and
everything else is displayed as a link. Similarly, for
countries, after the check
{% if c.pk == cat_selected %},
it is displayed either as text or a link.
Let's make a few more corrections. When using the
show_category function, enter a non-existent
category in the browser, for example,
http://127.0.0.1:8000/category/11/
After this, the function will generate an empty list,
which is not good practice. It would be better if a 404
page is generated for a non-existent category. Let's do
it as follows. If any category turns out to be empty, we
will generate a 404 page. Go to the views.py file and
add the following condition in the show_category
function:

def show_category(request, cat_id):


posts = Dir_travel.objects.filter(cat_id=cat_id)
cats = Category.objects.all()

if len(posts) == 0:
raise Http404()

context = {
'posts': posts,
'cats': cats,
'menu': menu,
'title': ' Display by Categories ',
'cat_selected': cat_id,
}
return render(request, 'traveler/index.html',
context=context)

We also need to import this function

from django.http import HttpResponse


from django.shortcuts import render, redirect
from django.http import Http404

from django.http import HttpResponse


from django.shortcuts import render, redirect
from django.http import Http404
udef show_category(request, cat_id):
posts = Dir_travel.objects.filter(cat_id=cat_id)
cats = Category.objects.att()

if len(posts) == 0:
raise Http4G4C)

context = {
' posts': posts,
'cats': cats,
'menu' : menu,
52 'title': ' Display by category ',
'cat_selected': cat_id,

55 Q return render(request, 'traveler/index.html', context=context)

If the number of posts is equal to '0', we will generate a


404 error. Let's check it. Click on the 'Asia' tab, and
since there are no posts yet, we will receive a 404
error.

IPage not found (404)


Request Method: GET
Request URL: http://127.0.0.1:8000/category/2/
Raised by: traveler.views.show_category

In debug mode, this is sufficient for demonstration


purposes. On a production server, we would redirect to
a custom error page.
Let's add the following information to this list.
Continents
Moldova
Europe
Asia MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA)

, IS A LANDLOCKED COUNTRY IN EASTERN ...


Africa
Asia

North America
Germany
South America GERMANY (GERMAN: DEUTSCHLAND, PRONOUNCED ['DDyTSLANT] (LISTEN)), OFFICIALLY THE FEDERAL REPUBLIC ...

Our channel

O YouTube Ukraine
UKRAINE (UKRAINIAN: YKPAIHA, ROMANIZED: UKRAINA, PRONOUNCED [UKRV'JINV] (LISTEN)) IS A ...

Let's add the category name and the post creation time
to this list. To do this, open the index.html file, find
where the list is being generated, and modify the lines.

{% extends 'traveler/base.html' %}

{% block content %}
<ul class="list-articles">
{% for p in posts %}
<li><div class="article-panel">
<p class="first"> Continent: {{p.cat}}</p>
<p class="last">flama:
{{p.time_update|date:"d-m-Y H:i:s"}}</p>
</div>

<h2>{{p.title}}</h2>
{% autoescape off %}
<p>{{p.content|truncatewords:10|upper}}</p>
{% endautoescape %}
<div class="clear"></div>
<p class="link-read-post"><a href="{{
p.get_absolute_url }}"> Read the post </a></p>
</li>
{% endfor %}
</ul>

{% endblock %}

extends 'traveler/base.html

{% block content %}
<ul class="list-articles">
{% for p in posts %}
<lixdiv class="article-panel">
<p class="first">Continent: {{p.cat}}</p>
<p class="last">date : {{p.time_update|date: "d-m-Y H:i:s"}}</p>
</div>

<h2>{-[p .title}}</h2>
{% autoescape off %}
< p>{{p.content|truncatewords:10|upper}}</p>
endautoescape %}
<div class="clear"x/div>
< p class="link-read-post"xa href="{{ p. get_absolute_url }}">ReadDost</ax/p>
</li>
■ {% endfor %}
</ul>

{% endblock %}

Here we display the continent using the variable p.cat.


This attribute will return the category name since we
have overridden it with the method

def_ str_ (self):


return self.name

i.e., 'cat' will refer to an instance of the Category


class, and the method will be triggered
def_ str_ (self):
The date is formatted in this way
{{p.time_update|date:"d-m-Y H:i:s"}}
i.e., we access the 'time_update' attribute and display
the date using the
filter |date:"d-m-Y H:i:s" - day-month-year
hours:minutes:seconds.
Let's see how it works by refreshing the page..

Continent Europe Date:; 23-10-2022 12:07:03

Moldova
MOLDOVA (ROMANIAN: REPUBLICA MOLDOVA)
, IS A LANDLOCKED COUNTRY IN EASTERN ...
Read post

Continent: Europe Date: 12-10-2022 12:30:09

Germany
GERMANY (GERMAN: DEUTSCHLAND, PRONOUNCED [ DDyTLLANT] (LISTEN)), OFFICIALLY THE FEDERAL REPUBLIC ...

Read post
Admin panel. Registering the
Category model

For starters, I would like to add one more field named


'Publication'.

How to do it?
It's simple! Open the admin.py file that we've worked
with before and add 'is_published'.

class Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo',
'is_puplished')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')

I
Bclass Dir_travelAdmin(admin.ModelAdmin):
7 Of list_display = ('id1, 'title', 'time_create', 'photo', 'is_puplished')
8 of list_display_links = ('id', 'title')
9 Of h search_fields = ('title', 'content')
10

To display this field in Russian within the 'Dir_travel'


class in the models.py file, add the line
verbose_name = "Publication" to the 'is_published'
field.

class Dir_travel(models.Model):
title = models.CharField(max_length=255, verbose_name
= 11 Title ")
content = models.TextField(blank=True, verbose_name =
" Article text")
photo =
models.ImageField(upload_to=''photos/%Y/%m/%d/'',
verbose_name = " Photo ")
time_create =
models.DateTimeField(auto_now_add=True, verbose_name
= " Creation time ")
time_update = models.DateTimeField(auto_now=True,
verbose_name = " Modification time ")
is_puplished = models.BooleanField(default=True,
verbose_name = " Publication ")
cat = models.ForeignKeyCCategory',
on_delete=models.PROTECT, null=True)

1 class Dir_travel(models.Model):
title = models.CharField(max_length=255, verbose_name = "header")
content = models.TextField(blank=True, verbose.name = "Articletext")
photo = models.ImageField(upload_to="photos/%Y/%m/%d/" , verbose_name = "Photo")
time_create = models.DateTimeField(auto_now_add=True, verbose_name = "Time of creation")
time.update - models.DateTimeField(auto_now=True, verbose_name = "Changetime")
is_puplished = models.BooleanField(default=True, verbose_name = "Publication")
cat = models.ForeignKeyCCategory1, on_delete=models.PROTECT, null=True)

Next, let's register our Category model.


Open the admin.py file and add the second class
CategoryAdmin in it.
class CategoryAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
list_display_links = ('id', 'name')
search_fields = ('name',)

class CategoryAdmin(admin . ModelAdmin):


©t list_display = ('id', ’name1)
©t list_display_tinks = ('id', 'name')
©t search_fields = (’name’,)

Make sure to put a comma after 'name' in the last line


as it's part of a tuple!
search_fields = ('name',)

And, in the next line, we will register the Category


model

admin.site.register(Category, CategoryAdmin)

a>iin.site.register(0ir_travel, Dir_travelAdmin)
20 admin.site.register(Category, CategoryAdmin)

Go to the admin panel and refresh/update it!


Django administration
Home>> Travel Categories

Select category to change

Find

v Run

□ ■ NAME

Categorys + Add
□ 6 South America

All countries + Add


□ 5 North America

□ 4 Asia

□ 3 Africa
□ 2 Asia

« □ 1 Europe

6 categorys

To display everything in Russian, let's add a nested


class Meta within the Category class in the models.py
file..

class Meta:
verbose_name = " Continent "
verbose_name_plural = " Continents "
ordering = ['id']
Aclass Category (models. Model) :
29 name = models.CharField(max_length=100, db_index=True)
30
3i i def __str__(self):
return self.name

34 l-J def get_absolute_url(self):


return reverse('category1, kwargs={'cat_id1: self.pk})
36
37
38 0 class Meta:
39 verbose_name = "Continent"

40 verbose_name_plural = "Continents"

41 ordering = [1 id']
42

Instead of 'name', let's also write the word 'Category'

class Category(models.Model):
name = models.CharField(max_length=100,
db_index=True, verbose_name = " Continent ")

def_ str__(self):
return self.name

def get_absolute_url(self):
return reverse('category', kwargs={'cat_id': self.pk})
Let's move to the admin panel and update

Groups + Add

Users Add

Action: Selected 0 items out of 6

TRIPS
□ ID CONTINENT

All countries •Add


O 1 Europe
Continent + Add
□ 2 Asia

□ 3 Africa

O 4 Asia

O 5 North America

« □ 6 South America

The altered meta description that we specified in both


models will be entered into the database tables. Let's
create another migration file using the command
python manage.py makemigrations

(venv) PS C:\Python\Django\travels\travels> python manage.py makemigrations


Migrations for 'traveler':
traveter\migrations\0003_alter_category_options_atter_category_name_and_more . py
- Change Meta options on category
- Alter field name on category
- Alter field is_puplished on dir_travel
(venv) PS C:\Python\Django\travels\travels>

Apply all these migrations to the database table


python manage.py migrate
(venv) PS C:\Python\Django\travels\travels> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, traveler
Running migrations:
Applying traveler.00G3_alter_category_options_alter_category_name_and_more... OK
(venv) PS C:\Python\Django\travels\travels> F|

After all these steps, we can say that the admin panel is
set up and fully functional at this stage. Next, using it,
we'll add a few more entries. Let's add 2-3 more
countries and link them to relevant categories. I've
taken the text from Wikipedia, and we'll use random
photos from the internet as well.

Once we exit the admin panel and refresh the page,


we'll see several more entries—exactly the number that
we posted through the admin panel.
If you don't upload a photo, Django framework will
throw an error reminding you to upload a photo
because it's a mandatory field. Let's check how our
photos were uploaded and the structure of their
placement.
Photos were automatically uploaded to the server, as
we can see. Let's add the display of the photos directly
in the template.
Open the index.html template and place the lines

<li><div class="article-panel">
<p class='"first'"> Continent: {{p.cat}}</p>
<p class='"last'"> date: {{p.time_updateldate:'"d-m-Y
H:i:s'"}}</p>
</div>
{% if p.photo %}
<p><img class="img-article-left thumb" src="
{{p.photo.url}}"></p>
{% endif %}

<h2>{{p.title}}</h2>

{% block content %}
<ul class="list-articles">
■{% for p in posts %}
<lixdlv class="article-panel">
<p class="first">Continent: {{p . cat}}</p>
<p class="last">datea:{{p.time_update|date:"d-m-Y H:i:s"}}</p>
</div>
■{% if p.photo %}
<pximg class="img-article-left thumb" src="{{p.photo.url}}"x/p>
{% endif %}

< h2>{{p.title}}</h2>
■ (% autoescape off %}
< p>{{p.content|truncatewords:10|upper}}</p>
■ {% endautoescape %}

Let's check if the 'photo' field is empty


{% if p.photo %}
If this field isn't empty, then we display this photo using
the attribute
{{p.photo.url}}

Let's check how this works. Navigate to the page and


refresh it.

If you click on the 'Europe' tab, you'll see posts about


European countries. Clicking on the 'South America'
tab will display posts about countries in South America.
Clicking on a tab where there are no posts yet will
result in a 404 error.

Hence, the category selection is functioning correctly.


Let's further enhance our admin panel. We'll make the
'publication' field editable.
Action: Run \ Selected 0 items out of 7
------- ---------------------

□ IO HEADING 2 A TIME OF CREATION 1 w PHOTO PUBLICATION

o 8 Canada November 16,2022 12:20 pm photos/2022/11 /16/Canada.PNG ®

□ 7 Venezuela November 16,2022 12:20 pm photos/2022/11 /16/Venezuela.PNG e

□ 6 Peru November 16,2022 12:15 pm photos/2022/11 /16/Peru,PNG e

o 5 Moldova October 13,2022 11:08 am photos/2022/10/13/Museu m_of_History.jpg

□ 4 Germany October 12, 2022 12:30 pm photos/2022/10/12/germany,jpg ©

□ 3 Ukraine October 12,202212:27 pm photos/2022/10/12/1597127429_5.jpg ©

OiPoima Poland October 12,202212:12 pm photos/2022/10/12/1 _6bTr3bq.jpg

7 AH countries

To do this, let's navigate to the admin.py file and


within the Dir_travelAdmin class, add another
attribute

class Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo',
'is_puplished')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)

I made a mistake earlier.

dgango.core.management.base.SystemcheckError: SystemCheckError: System check identified some issues:

ERRORS:
cclass 1 traveler.admin.Dir_travelAdmin’>: (admin.E121) The value of 'list_editable[0]' refers to 'is_published', which is not a field of 1 traveler.Dir_travel'.

Let's fix that! Instead of 'is_puplished', we'll write


'is_published'. Also, we'll need to rerun the migration
commands for the database

python manage.py makemigrations


python manage.py migrate

Should be

class Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id1, 'title', 'time_create', 'photo',
'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)

Rclass Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id1, 'title', 'time_create', 'photo', 'is_published1)
! Of list_display_links = ('id', 'title')
Of search_fields = ('title', 'content')
) Of list_editable = ('is_published ',)

Let's check. Refresh the admin panel. Don't forget to


start the server.
Action; --------- vll Run Selected 0 items out of 7

□ "> HEA0W6 2 CREATION TIME 1 „ PHOTO PUBLICATION

□ 8 Canada November 16,202212:20 pm photos/2022/11/16/Canada. PNG □


□ 7 Venezuela November 16,202212:20 pm photos/2022/11 /16/Venezuela.PNG □
□ 6 Peru November 16,2022 12:15 pm photos/2022/11 /16/Peru.PNG □
□ 5 Moktova Octo tser 13, 2022 11:08 am photos/2022/10/13/Muse u m_of_H i sto ry.jpg □
□ 4 Germany October 12,2022 12:30 pm photos/2022/10/12/germany.jpg Q
□ 3 Ukraine October 12,2022 12:27 pm photos/2022/10/12/1597127429_5.jpg □
□ 2 Poland October 12, 2022 12:12 pm photos/2022/10/12/1 _6bTr3bq.jpg □
7 All countries
Save

Now we have that field editable.

Let's add more fields that we can use to filter our list of
articles. We'll do this by adding another line to the
same class.

class Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo',
'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
listfilter = ('is_published', 'timecreate')

□class Dir_travelAdmin(admin.ModelAdmin) :
7 ©T list_display = ('id', 'title', 'time_create', ' photo', is_published')
list_display_links = ('id', 'title')
9 ©T search_fields = ('title', 'content')
10 ©T list_editable = ('is_published',)
11 ©T list_fliter = ('is_published', 'time_create')

'is_published', 'time_create' - fields by which we'll


filter our list. Let's refresh the page.
ADD ALL COUNTRIES +]
Select All countries to change

Q | |[ Find |

1 Publication

Action: |--------- *» || Run j selected 0 items out of 7 Bee

□ o heaong 2 * TIME OF CREATION 1 v P”010 PUBLICATION No

□ 8 Canada November 16. 202212:20 pm photoS/2022/11/16/Canada.PNG Q


1 Creation time

□ 7 Venezuela November 16,202212:20 pm photos/2022/11/16/Venezuela.PNG S Any date

□ 6 Peru November 16,202212:15 pm photos/2022/11/16/PetU.PNG S


Last 7 days
□ 5 Moldova October 13,202211:08 am photos/2022/10/13/Museum_of_History.jpg S This month
This year
□ 4 Germany October 12,202212:3o pm photos/2022/10/12/germany.Jpg S

□ 3 Ukraine October 12,2022 12:27 pm photos/2022/10/12/1597127429_5.jpg □

□ 2 Poland October 12,202212:12 pm photos/2022/10/12/1 _6bTr3bq.jpg D

7 All countries

We see the sidebar with filters in the top right corner.


User-defined template tags.
Earlier, we created two functions: index and
show_category. They are quite similar and violate the
principle of "Don't Repeat Yourself" (DRY). For
instance, the line cats = Category.objects.all() exists
in both functions. Let's eliminate this duplication by
using custom template tags as an example.

In Django, you can create two types of tags

Simple tags - simple tags


Inclusion tags - inclusion tags.
For more details on the topic, you can read by following
the link below
https://django.fun/ru/docs/django/4.1/howto/cust
om-template-tags/

Let's see how to use this feature with a specific


example.
Simple tags
Let's move to the project.
Open the views.py file.
Initially, we'll create a simple tag that fetches
categories from the database and can be used directly
in the template.
According to the Django documentation, all tags
should be placed in a specific subdirectory within our
app, and we'll name it templatetags.

The directory should indeed be a package, which


means it needs to contain a special file named
__init__.py, and this file will remain empty. This file
simply informs Django that the directory is a package.
We'll define the logic for the new tag in a file named
traveler_tags.py.

v M LdlipljlL-

v ta traveler
about.html
base.html
index.html
v El templatetags
£_init_.py

travel er_tags.py
ft Jnit_.py

And let's import the 'templates' module at the


beginning to work with templates and our models.

from django import template


from traveler.models import *

Let's register the tag templates by creating a Library


class

Register = template.Library()

Next, we'll define a function to work with a simple tag.


Since our tag will return categories, let's name it
accordingly

def get_categories():
return Category.objects.all()

In this function, we'll access the database and select all


entries from the categories table, which will be returned
by this function.
from django import template
from traveler.models import *

Register = template.Library()

def get_categoriesQ :
return Category.objects.allO

Next, we need to turn this function into a tag. To do


this, we'll use a special decorator.

from django import template


from traveler.models import *

Register = template.Library()

@register.simple_tag()
def get_categories():
return Category.objects.all()

from django import template


from traveler.models import *

Register = template.Library()

^register.simple_tag()
def get_categories():
return Category.objects.altO
9
This simple process turns our function into a tag that
can be used in the templates of our application. Next,
let's open the base template, base.html, and load our
tag at the very beginning of the file.

{% load static %}
{% load traveler_tags %}

toad static %}
{% toad traveter_tags %}
<!DOCTYPE htmt>

In this case, it currently contains just one simple tag


that we can use in this template.
Let's save it
<!--Sidebar To the left -->
<td valign="top" class="left-chapters">
{% get_categories %}
<ul id="leftchapters">

And let's see how this simple tag works. Let's navigate
to the terminal and start our test server.

We have an error

<5 File "C:\Python\Django\travels\venv\lib\site-packages\django\ternptate\backends\cljango. py", line 117, in get_installed_libraries


g File ”C:\Python\Django\travels\travels\traveter\templatetags\traveler_tags.py ", line 6, in <module>
H return {
File "C:\Python\Django\travets\venv\lib\site-packages\django\template\backends\django.py", line 117, in <dictcomp>
•g ^register. simple_tag ()
£ NameError: name 'register' is not defined. Did you mean: 'Register'?

Let's correct 'Register' to 'register'


from django import template
from traveler.models import *

register = template.Library()

(^register. simple_tag ()
def get_categories():
return Category.objects.allO
9

Let's restart our server. The command is...


Python manage.py runserver

We see on the top left our list, which appeared thanks


to the tag that simply returns a list of categories from
the database. Let's iterate through our tag.
Since this is a tag, we won't be able to use it directly in
a loop. Therefore, we need to first create a reference to
this tag using a variable.
<!--Sidebar On the left -->
<td valign="top" dass="left-chapters">
{% get_categories as categories %}
Now we can work in a loop using this variable.

{% for c in categories %}
{% if c.pk == cat_selected %}
<li class='"selected'">{{c.name}}</li>
{% else %}
<li><a href="{{ c.get_absolute_url }}'">
{{c.name}}</a></li>
{% endif %}
{% endfor %}
{% for c in categories %}
-{% if c.pk == cat_selected %}
<li class="selected">{{c.name}}</li>
{% else %}
<lixa href="-{-{ c.get_absolute_url }}">-{{c.name}}</ax/li>
endif %}
{% endfor %}

Next, let's go to the main page, refresh, and observe


that everything works without changes. Then, we'll
rewrite the functions show_category and index in the
views.py file

def index(request):
posts = Dir_travel.objects.all()
context = {
'posts': posts,
'menu': menu,
'title': 'The main page',
'cat_selected': 0,
}
return render(request, 'traveler/index.html',
context=context)

def show_category(request, cat_id):


posts = Dir_travel.objects.filter(cat_id=cat_id)

if len(posts) == 0:
raise Http404()

context = {
'posts': posts,
'menu': menu,
'title': 1 Display by categories ',
'catselected': cat_id,
}
return render(request, 'traveler/index.html',
context=context)

Let's refresh the page again. Everything is working fine.


Additionally, if needed, we can change the tag name.
Let's write it like this:

@register.simple_tag(name='getcats')
def get_categories():
return Category.objects.all()
from django import template
from traveler.models import *

register = template.LibraryO

(^register. simple_tag (iiame= ’ getcats')


def get_categories():
return Category.objects.allO

And now in the template, we can use this name

<!--Sidebar On the left -->


<td valign="top" dass="left-chapters">
{% getcats as categories %}
<ul id="leftchapters">
34 <!--Sidebar cneea
<td valign-"top" class="left-chapters">
{% getcats as categories %}
<ul id-"leftchapters">
{% if cat_selected == 0 %}
<li class=" selected">Continents </li>

Let's refresh the page. Everything is working again.


Let's now consider the second type of custom template
tags.
Inclusion tag
Inclusion tags allow the creation of a custom template
based on certain data and return an HTML snippet.
Previously, we returned not an entire HTML page but a
collection of data that was then used in the template.
That is, in the base.html file, we can take an entire
fragment of code, place it in a separate template file,
and put a small tag in the base file. Let's start. First, in
the traveler_tags.py file we previously created, let's
define the second inclusion tag.

@register.inclusion_tag('traveler/list_categories.htmr
)
def show_categories():
cats = Category.objects.all()
return {"cats": cats}
1 □from django import template
2 tifrom traveler. models import *
3
4 register = template.Library ()
5
6 (^register. simple_tag (name= ' get cats ' )
7 Hdef get_categories():
8 return Category.objects.all()
9
10 ^register.inclusion_tag(1traveler/list_categories.html')
11 Hdef show_categories():
12 cats = Category.objects.all()
13 return {’cats": cats}
14

The name of this tag is show_categories because it


will return a complete page.
cats = Category.objects.all() - This function will
retrieve all categories.
{"cats": cats} - It returns a dictionary.
The parameter cats will be automatically passed to the
template 'traveler/list_categories.html', which we
will define below.
This template, list_categories.html, will generate an
HTML page fragment.
Let's place this template in the same folder where all
the HTML files are located..
v ft traveler
tfS about.html

itJ base.html
17^ index.html

list_categories.html
El tern platetags

Let's copy the code fragment from base.html and


place it in the file list_categories.html

{% for c in categories %}
{% if c.pk = = cat_selected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li><a href="{{ c.get_absolute_url }}">
{{c.name}}</a></li>
{% endif %}
{% endfor %}

We need to change the line {% for c in categories


%} to {% for c in cats %}, which we obtain from
return {"cats": cats}

{% for c in cats %}
{% if c.pk == cat_seiected %}
<li eta ss=" selected" >{-{c .name}}</ti>
{% else %}
<lixa href="-[{ c.get_absolute_url }}">-{-[c. name}}</ax/li>
{% endif %}
7 {% endfor %}-

Let's go back to the base.html file and instead of...

{% for c in categories %}
{% if c.pk = = cat_selected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li><a href="{{ c.get_absolute_url }}">
{{c.name}}</a></li>
{% endif %}
{% endfor %}

Let's write...

{% show_categories %}

<U><a href="-{% url 'home' %}">Continents</ax/li>


{% endif %}

{% show_categories %}

<li class="share">
<p> Our channel </p>

And remove the line...


{% getcats as categories %}

<td vatign=,1top’1 ctass="left-chapter's">


36 {% getcats as categories %}
<ul id^'Ieftchapters'T>
if cat_setected == 0 %}

Let's check how everything works after the changes.


Start our server and refresh the page. As we can see,
everything is working.
Let's remove the {% show_categories %} tag and
see how it affects the functionality of our page

The sidebar menu is missing. Everything is working, but


with some adjustments.
The sidebar menu currently doesn't work fully.
When selecting a menu item under the 'Continents'
button, such as 'Europe,' it doesn't get highlighted in
any way to emphasize the selection of that category.
We'll fix this now by passing parameters to the tags.
Let's open the 'traveler_tags.py' file and see how we
can pass parameters to our simple tag.
For this tag function, we can define a named
parameter, for example, 'filter=None,' which filters
data based on the categories table.

@register.simple_tag(name='getcats')
def get_categories(filter=None):
if not filter:
return Category.objects.all()
else:
return Category.objects. filter(pk=filter)

If our filter has a value of None, i.e., filter=None,


then we simply select everything that exists in these
categories,
return Category.objects.all()
If it takes any other value, then we use the respective
method
return Category.objects. filter(pk=filter)

and select the 'pk' that corresponds to the specified


filter. Let's see how the 'filter' parameter can be
passed to the simple tag 'get_categories,' which is
named 'getcats' in the base.html template.
Open the base.html template and insert the line...
<!--Sidebar Left -->
<td valign="top" dass="left-chapters">
{% getcats %}
<ul id="leftchapters">
<!--Sidebar cneBa -->
<td vatign="top" class="left-chapters">
{% getcats %}
= <ul id="leftchapters">

To simply see how it works and view the entire full list.
About the site

<QuerySet [<Category: Europe>,


<Category: Asia>, <Category:
Africa>, <Category: Asia>,
<Category: North America>,
<Category: South America>]>

Continents

Europe

Asia

Africa

Asia

Next, we'll pass the parameter 'filter' and assign it


some value
{% getcats filter=1 %}

So, only one entry with id = 1 should be selected. We


navigate to the page, refresh, and indeed, we see only
one entry.
Now let's perform a similar operation for the inclusive
tag and pass two parameters instead.
@register.indusion_tagCtraveler/list_categories.html')
def show_categories(sort=None, cat_selected=0):
if not sort:
cats = Category.objects.all()
else:
cats = Category.objects.order_by(sort)
return {"cats": cats, "cat_selected": cat_selected}

The first parameter, sort=None, defines the sorting of


these categories.
The second one, cat_selected=0, determines which
category is selected.
Next, we perform the sorting. If it's not defined, then..
cats = Category.objects.all()
and if it's defined, we sort by the specified field
cats = Category.objects.order_by(sort)

The second parameter


'cat_selected': cat_selected
is passed directly to the
list_categories.html
template so that we can check which category is
selected and display it as regular text.
Now, in the base.html template, we'll remove the {%
getcats filter=1 %} tag we used for the example and
correct it in the line below to
{% show_categories '-name' cat_selected %}.
Here, the filtering will occur based on the name '­
name', and cat_selected will be passed, which is
available in this template as
{% if cat_selected == 0 %}...

<ul id='"leftchapters'">
{% if cat_selected == 0 %}
<li class='"selected'"> Continents </li>
{% else %}
<li><a href='"{% url 'home' %}'"> Continents </a>
</li>
{% endif %}

{% show_categories '-name' cat_selected %}

<ul id="teftchapters">
38 {% if cat_selected == 0 %}
<li class=" selected"> Continents </li>
else %}
<lixa href="{% url 'home' %}" Continents </ax/li>
{% endif %}

{% show_categories '-name' cat_selected %}


Let's see how this will work. Let's refresh the page. We
see that upon entering the category, the link
disappears and is displayed as text, and the order
changes. Everything is working.

Continents

South America Continent: South America

North America Venezuela


Europe
THE CONTINENTAL TERRITORY IS BORDERED ON THE NORTH BY THE ...

Africa

Alternatively, if sorting isn't necessary, we can pass just


one parameter:
{% show_categories cat_selected=cat_selected
%}.
Let's refresh the page, and we see that nothing has
changed.
_K(

The use of slugs in URL addresses


Let's display web pages based on their slug.
A slug is a unique part of a URL that's associated with a
specific record and consists of letters, underscores, and
numbers. It's a unique set of characters used to
retrieve an article from the database.
For instance, in the
URL https://center-doors.com.ua/mezhkomnatnye-dveri ,
'mezhkomnatnye-dveri' is the slug in this practice.
Using slugs is a great practice because such pages are
better ranked by search engines and are more
understandable to end-users.
In contrast, there's another approach that's less
readable and poorly ranked by search engines, like in
the URL:
httPs://holz.ua/ua/dveri/vhodnye/?gclid=Cj0KCQiA-
JacBhC0ARIsAIxybyP9dhetBuWeBpP9QlZ2wq7eGP7Nmq
Ptdns3sUnJ2q5RRsd6woUNAZsaAhXZEALw wcB
Firstly, let's display articles by their identifier, and then
replace the address with the slug.
Open the views.py file where we have a placeholder
function called show_post - to display the article. Let's
modify it slightly to display the article based on its
identifier

def show_post(request, post_the):


post = get_object_or_404(Dir_travel, pk=post_the)

context = {
'post': post,
'menu': menu,
'title': post.title,
'catselected': 1,
}
return render(request, 'traveler/post.html',
context=context)
Hdef show_post(request, post_the):
post = get_object_or_404(Dir_travel, pk=post_the)

context = {
'post1 : post,
40 * 'menu': menu,
'title': post.title,
'cat_selected': 1,
}
return renderfrequest, 'traveler/post.html', context=context)

Rdef show_category(request, cat_id):


posts = Dir_travel.objects.filter(cat_id=cat_id)

We start by taking a record from the Dir_travel model,


where the primary key 'pk' corresponds to the
'post_the' identifier we pass in the request.
We use the 'get_object_or_404' function.
It selects a post from the Dir_travel model with the
primary key 'pk=post_the' if it exists.
Otherwise, it raises a 404 exception. To use this
function, it needs to be imported..

from django.shortcuts import render, redirect,


get_object_or_404

from django.http import HttpResponse


from django.shortcuts import render, redirect, get_object_or_404

Next, we form parameters

context = {
'post': post,
'menu': menu,
'title': post.title,
'cat_selected': post.cat_id,
}

We'll pass these to the post.html template, which, by


the way, we don't have yet.
The line 'cat_selected': 1, will be changed to
'cat_selected': post.cat_id,

That means we're passing the property cat_id, which


exists in the Dir_travel object.
In other words, it's a reference to an object of class
Dir_travel(models.Model):
And when an instance of this class is created, cat_id is
automatically generated, containing the identifier of the
current category related to the articles.
Then, we pass all these parameters to the template

return render(request, 'traveler/post.html',


context=context)

All our templates are located in the folder traveler

v templates
v M traveler
iff about.html
base.html
iff index.html
।list_categories.html
> El templatetags

Let's create this file.


Instead of the automatically generated template, we
add our base template.

{% extend 'traveler/base.html' %}
{% block content %}
<h1>{{post.title}}</h1>
{% if post.photo %}
<p><img class="img-article-left" src="
{{post.photo.url}}"></p>
{% endif %}
{{post.contentllinebreaks}}
{% endblock %}
{% extend 'traveler/base.html' %}

{% block content %}
<hl>{{post.title}}</hl>

{% if post.photo %}
<p><img class="img-article-left" src="{{post. photo. url}}"x/p>
{% endif %}

{{post.content|linebreaks}}
{% endblock %}

In it, we extend the base template


{% extend 'traveler/base.html' %}
In the content block, we display a first-level heading
<h1>{{post.title}}</h1>
If there's a photo
{% if post.photo %}
then we show the photo

<p><img class="img-article-left" src="


{{post.photo.url}}"></p>
That is, we pass post.photo.url of the image
associated with the post
and then display the content
{{post.content|linebreaks}}
with the
|linebreaks filter, which adds paragraph tags to our
article.
Additionally, we have the route specified.
path('post/int:post_the/', show_post,
name='post'),
Let's see how this will work.
We'll start the web server.

We click on the 'Read Post' button and navigate to the


page

Page with the full article will open.


Additionally, the category 'North America' is selected,
while the other categories are highlighted as links, and
we can navigate to them as well.
All this is made possible thanks to the line
'cat_selected': post.cat_id.
If an invalid identifier is set in the search bar, for
example
http://127.0.0.1:8000/category/141/
, then we will encounter a 404 error.

<- -> O (D 127.0.0.1:8000/category/141/

Page not found (404>


Request Method: GET
Request URL: http://127.0.0.1:8000/category/141/
Raised by: traveler.views. show_category

Let's display articles by slug.


For this, in models.py, we'll add one more field.

class Dir_travel(models.Model):
title = models.CharField(max_length=255, verbose_name
= "Header")
slug = models.SlugField(max_length=255,
unique=True, db_index=True, verbose_name="URL")
content = models.TextField(blank=True, verbose_name =
" Article text")
photo =
models.ImageField(upload_to="photos/%Y/%m/%d/",
verbose_name = "Photo")
time_create =
models.DateTimeField(auto_now_add=True, verbose_name
= " Time of creation ")
time_update = models.DateTimeField(auto_now=True,
verbose_name = " Time of modification ")
is_published = models.BooleanField(default=True,
verbose_name = " Publication ")
cat = models.ForeignKeyCCategory',
on_delete=models.PROTECT, null=True, verbose_name = "
Category")

1-1 from django.urls import reverse

4
E

Hclass Dir_travel(models.Model):
title = models.CharField(max_length=255, verbose_name - "header")
slug = models.SlugField(max_length=255, unique=True, db_index=True, verbose_name="URL")
content = models.TextField(blank=True, verbose_name = "Articletext ")
photo = models.ImageField(upload_to="photos/%Y/%m/%d/", verbose_name = "Photo")
time_create = models.DateTimeField(auto_now_add=True, verbose_name - "Time of creation ")
time_update - models.DateTimeField(auto_now=True, verbose_name - "Chanoetime ")
is_published = models.BooleanField(default=True, verbose_name = "Publication")
cat = models.ForeignKeyCCategory', on_delete=models.PROTECT, null=True, verbose_name = "Cateqorv")

16 ©t i-J def __str_ (self):


• return self.title
18
def get_absolute=url(self):
return reverseCpost', kwargs={1 post_the ': self.pk})

For this purpose, there is a special class called


SlugField.
max_length=255 - maximum length for this slug.
unique=True - this field will be unique, as each
article's slug must be unique.
db_index=True - the field will be indexed for faster
searching by this slug.
verbose_name="URL" - in the admin panel, we'll see
the URL field.

Since we added a new attribute, accordingly, we need


to change the database table as well.
For this, in the terminal, we'll execute the familiar
command
Python manage.py makemigrations
Resulting in the following message:
(venv) PS C:\Python\Diango\travels\travels> Python manage.py makemigrations
It is impossible to add a non-nullable field 'slug' to dir_travel without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option: Fl

Which informs us that the field cannot be empty


because our table already contains records, and when
we add a new field, it cannot be empty by requirement.
Additionally, this field should also be unique.
Let's make some adjustments.
The first thing we'll do is remove the highlighted
parameter in the line
cat = models.ForeignKey('Category',
on_delete=models.PROTECT, null=True,
verbose_name="Category")
and rebuild our database structure.
We'll delete all migrations.
|S| Project ▼ O T iQt —
v M travels sources root C:\Python\Django\travels
v M travels
> M media
> M static
v El traveler
v migrations
^OOOIJnitial.py

Q002_cate g o ry_a Ite r_d i r_tra ve l_o pti o n s_a n d_m o re. py
D0D3_a Iterjcate g o ry_o pti o n s_a Iterjcate g o ry_n a m e_a n d_r
0004_alter_dir_travel_cat.py
D005_rena m eJ s_p u p I i shed_d i r_tra vel J s_p u b I i sh ed. py
&_init_.py
> ta static
v B templates
v M traveler
about.html
We'll remove the checkmarks to physically delete from
the hard drive

03 De ete X

Delete 5 files?

Safe delete (with usage search)

Search in comments and strings

? OK Cancel

And click 'OK'.


Next, press '2' to exit

Please select a fix:


1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option: 2^1

Let's immediately add a slug to the Category class as


well to avoid unnecessary migrations.

class Category(models.Model):
name = models.CharField(max_length=100,
db_index=True, verbose_name = " Continent")
slug = models.SlugField(max_length=255,
unique=True, db_index=True, verbose_name="URL")

def_ str__(self):
return self.name
Hclass Category(models.Model):
name = models.CharField(max_leng1 =166, db_index=True, verbose_name = ''Continent'1)
slug = models.SlugFleld(max_leng1 =255, uniqi =True, db_index=lrue, verbose_namB="LRL")

33 0) def (self):
• return self.name
35
def get_absolute_url(self):
return reverse(’category', kwargs={'cat_id': self.pk})

Let's enter the command in the terminal once again


Python manage.py makemigrations

(venv) PS C:\Python\Django\travels\travels> Python manage.py makemigrations


Migrations for 'traveler':
traveler\migrations\0001_initial.py
- Create model Category
- Create model Dir_travel
(venv) PS C:\Python\Django\travels\travels> [

And we see that it has been applied.


v El traveler
v El migrations
Jnitial.py
j£_init_.py
> M static
v ta templates
v k- traveler

Next, we apply the migration using the command


'Python manage.py migrate.'
We see that it doesn't get created because the
database already contains such tables, and it simply
cannot be rebuilt.
(venv) PS C:\Python\Django\travels\travels> Python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, traveler
Running migrations:
No migrations to apply.
(venv) PS C:\Python\Django\travels\travels> [

To do this, let's delete the previous database and


repeat the migration command.
(venv) PS C:\Python\Django\travels\travels> Python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, traveler
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.GG01_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.GGG3_logentry_add_action_flag_choices... OK
Applying contenttypes.0O02_remove_content_type_name... OK
Applying auth.GG02_alter_permission_name_max_length... OK
Applying auth.GG03_alter_user_email_max_length... OK
Applying auth.GGG4_alter_user_username_opts... OK
Applying auth.GGG5_alter_user_last_login_null... OK
Applying auth.GGG6_require_contenttypes_GGG2... OK
Applying auth.GGG7_alter_validators_add_error_messages... OK
Applying auth.GGG8_alter_user_username_max_length... OK
Applying auth.GGG9_alter_user_last_name_max_length... OK
Applying auth.GG10_alter_group_name_max_length... OK
Applying auth.GGll_update_proxy_permissions... OK
Applying auth.GG12_alter_user_first_name_max_length... OK
Applying sessions.0001_initial... OK
Applying traveler.0001_initial... OK
(venv) PS C:\Python\DJango\travels\travels> [

Everything was created as we see. The database table


completely rebuilt, including the admin panel. The
superuser created earlier has disappeared. Therefore, let's
create the superuser again to access the admin panel. To do
this, execute the following command:
Python manage.py createsuperuser
(venv) PS C:\Python\Django\travels\travels> Python manage.py createsuperuser
Username (leave blank to use 'user'): root

Email address: [email protected]

Password:
Password (again):
The entered password is too short. It must contain at least 8 characters.

The entered password is too wide.

The entered password consists of numbers only.

Bypass password validation and create user anyway? [y/N]: y


Superuser created successfully.
(venv) PS C:\Python\Django\travels\travels> F

We'll start the test web server and check. Enter the
command
'Python manage.py runserver'.
Then input your login and password

Django administration

Everything is working as before. However, if we enter


'Continents,' it will be empty because we deleted the
database.
Django administration

Home > Travel > Continents

Select Continent to change

0 Continents

If you click on the 'Add' button next to 'Continents' to


refill it with content, in the 'Continent' field, enter, for
instance, 'Europe,' and a little below, you'll need to
enter a URL address, such as 'europa'.

This is the slug fragment. We can input it manually, but


since the slug replicates the title, it would be good for
the slug to automatically populate in this field when the
title is filled.
This can be achieved as follows:
Open admin.py where the CategoryAdmin class is
located. We can add a line to this class

class CategoryAdmin(admin.ModelAdmin):
list_display = (id1, 'name')
list_display_links = ('id', 'name')
searchfields = ('name',)
prepopulatedfields = {"slug": ("name",)}

class CategoryAdmin(admin. ModelAdmin):


list_display = ('id', ’name1)
list_display_links = ('id’, 'name')
search_fields = ('name',)
prepopulated_fields = {"slug11: ("name",)}

Thanks to this entry, we can fill in the 'name' field,


based on which the 'slug' field will automatically be
filled in transliteration.
Let's check it.

Everything is working as intended. If, for any reason,


the slug isn't unique, we can always make adjustments
to it.
Add Continent

Continent:

evropa-2

Since all previous records in the database were deleted,


let's recreate all those records of continents that were
there before, following the same process.

© The continent "North America" has been successfully added.

Select Continent to change

Action: Run | 1 Selected 0 items out of 5

□ ID CONTINENT

O 1

O 2

□ 3

□ 4 South America

O 5 North America

5 Continents

Next, let's add all countries


Here, we also want the URL field to be automatically
filled in. We'll do everything similarly to the previous
example

class Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo',
'is_published')
list_display_links = ('id', 'title')
searchfields = ('title', 'content')
list_editable = ('is_published',)
listfilter = ('is_published', 'time_create')
prepopulatedfields = {"slug": ("title",)}

Rclass Dir_travelAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create’, 'photo', 'is_published')
8 Of list_display_links = ('id', 'title')
9 Of search_fields = ('title', 'content')
0 Of list_editable = ('is_published',)
1 Of list_filter = ('is_published', 'time_create')
2 Of A prepopulated_fields = {"slug": ("title",)}
Instead of 'name,' we write 'title.'
Let's fill the database together with uploading photos
for different countries.

After filling in, here is the resulting list of countries


Select All countries to change

Find
Q

Action:
—_______ v Run Selected 0 items out of 13

□ ID HEADING 2 * TIME OF CREATION 1 T PHOTO PUBLICATION

□ 13 Tanzania December 2, 2022 12:00 pm photos/2022/12/02/tanzania.jpg S


o 12 Zimbabwe December2, 2022 11:58 am photos/2022/12/02/zi mbabwe. jpg Q
□ 11 Cambodia December 2, 2022 11:57 am photos/2022/12/02/cambodia.jpg a
□ 10 China December 2, 2022 11:55 am photos/2022/12/0 2/china.jpg a
□ 9 Canada December 2, 2022 11:54 am photos/2022/12/02/Canada.jpg a
□ 8 United Stales December2, 2022 11:52 am photos/2022/12/02/usa.jpg a
□ 7 Mexico December 2, 2022 11:44 am photos/2022/12/02/mexico.jpg □
□ 6 Peru December 2, 2022 11:41 am photos/2022/12/02/Peru.jpg a
□ 5 Brazil December 2, 2022 11:39 am photos/2022/12/0 2/brazil .jpg Q
o 4 Italy Dece m ber 2, 2022 11:36 am photos/2022/12/0 2/italy.jpeg Q
□ 3 Germany December 2, 2022 11:35 am photos/2022/12/02/germany.jpg a
□ 2 Poland December 2, 2022 11:31 am photos/2022/12/02/poland.jpg a
□ 1 Ukraine December2, 2022 11:28 am photos/2022/12/02/ukraine.jpg Q
13 All countries
Save

Let's go to the website

If you click on the 'Read' button, you'll be directed to a


page with a detailed description
Europe Tanzania
Later in the Stone and Bronze Age, prehistoric migrations into Tanzania included Southern Cushitic speakers who moved south from present-day Ethiopia; [10] Eastern Cushitic people who
Tanzania from north ofLake Turkana about important
Many2,000 hominid
and 4,000 yearsfossils
ago;[10] been
haveand thefound in Tanzania,
Southern Nilotes,such
including the Datoog, whoPliocene
as 6-million-year-old hominid
originated fossils.
from the The genus
present-day Australopithecus
South Sudan-Ethiopia ranged
border across Africa
region between
between 2 milliot
and2,400
2,9004and yet
page 18 These movements took place atTanzania
and
about (tanzo
the oldest
the same ni:a/;[8][9][b]
remains
time asofthe Swahili:
genus [tanza
Homo
the settlement areni.a]),
of the found officially
MasharikinearBantu
Lake United
the Olduvai.
from West Africaofin
Republic
Following Tanzania
therise
the Lake (Swahili:
of Jamhuri
Homo erectus
Victoria yaTanganyika
1.8
and Lake Muungano
million years Tanzania),
waago,
areas. humanity
They a country
is spread
subsequently all in
over
migratedtheAfrica
East within
Old World,
across the
and
the rest African
oflater in thebeT
Tanzania
and 1,700 years ago.[10][ll] Lakes
and region. under
Australia It borders UgandaHomo
the species north; Kenya
to thesapiens. to thealso
H. sapiens northeast;
overtook AfricaIslands
Comoro and thethe
and absorbed Indian Ocean
older to the
species east; Mozambique and Malawi to the south; Zambia to the southwest; and
of humanity.
Burundi, and the Democratic Republic of the Congo to the west. Mount Kilimanjaro, Africa's highest mountain, is in northeastern Tanzania. According to the United Nations, Tanzania has a
of 63.59
duringmillion, making it the most populous country located entirely south ofThis equator.
the was


German rule began in mainland Tanzania the late 19th century when Germany formed German East Africa. followed by British rule after World War I. The mainland was governed as Tanganyika, with the Zanzil
South America Archipelago remaining a separate colonial jurisdiction. Following their respective independence in 1961 and 1963, the two entities merged in 1964 to form the United Republic of Tanzania. [12] Tanganyikajoined the British Cot
in 1961 and Tanzania remains a member of the Commonwealth as a unified republic.[13]

Tanzania's population is composed of about 120 ethnic,[14] linguistic, and religious groups. The sovereign state of Tanzania is a presidential constitutional republic and since 1996 its official capital city has been Dodoma where
president's office, the National Assembly, and all government ministries are located.[15] Dar es Salaam, the former capital, retains most government offices and is the country's largest city, principal port, and leading commercial
[ 16][17] Tanzania is a de facto one-party state with the democratic socialist Chama Cha Mapinduzi party in power.
Our channel
Tanzania is mountainous and densely forested in the north-east, where Mount Kilimanjaro is located. Three ofAfrica's Great Lakes are partly within Tanzania. To the north and west lie Lake Victoria, Africa's largest lake, and La
Tanganyika, the continent's deepest lake, known for its unique species of fish. To the south lies Lake Malawi. The eastern shore is hot and humid, with the Zanzibar Archipelago just offshore. The Menai Bay ConservationArea i
OYouTube largest marine protected area. The Kalambo Falls, located on the Kalambo River at the Zambian border, is the second-highest uninterrupted waterfall inAfiica.[18]

Christianity is the largest religion in Tanzania, but there are also substantial Muslim and animist minorities. [19] Over 100 different languages are spoken in Tanzania, making it the most linguistically diverse country in East Afrit
country does not have a de jure official language,[21][22] although the national language is Swahili.[23] Swahili is used in parliamentary debate, in the lower courts, and as a medium of instruction in primary school. English is t
foreign trade, in diplomacy, in higher courts, and as a medium of instruction in secondary and higher education; [20] although the Tanzanian government is planning to discontinue English as the primary language of instruction,:
available as an optional course.[24] Approximately 10% of Tanzanians speak Swahili as a first language, and up to 90% speak it as a second language.[20]

Currently, articles are being displayed by identifier.


However, we need to display them by slug.
Let's open urls.py and in the route
path('post/int:post_slug/', show_the,
name='post'),
make some modifications

urlpatterns = [
path(', index, name='home'),
path( about/, about, name='about'),
path(addpage/, addpage, name='add_page'),
path( contact/, addpage, name='contact'),
path( 1 login/, login, name='login'),
path( 1 post/<slug:post_slug>/, show_post, name='post'),
path( 1 category/<int:cat_id>/, show_category,
name=' category'),
(Jurlpatterns = [
path('', index, name='home'),
path('about/', about, name= 'about'),
path('addpage/', addpage, name='add_page'),
path(' contact/', addpage, name= 'contact'),
pathf'login/', login, name='login'),
path('post/<slug:post_slug>/', show_post, name='post'),
path('category/<int:cat_id>/', show_category, name=1 category'),
$|]

Next, in the views.py file, we'll also make changes to


the view function

def show_post(request, post_slug):


post = get_object_or_404(Dir_travel, slug=post_slug)

context = {
'post': post,
'menu': menu,
'title': post.title,
'cat_selected': post.cat_id,
}
return render(request, 'traveler/post.html',
context=context)
35 Adef show_post(request, post_the):
36 post = get_object_or_AGA(Dir_travel, pk=post_the)
37
38 context = {
39 'post': post,
AO 'menu': menu,
Al 'title1: post.title,
A2 'cat_selected': post.cat_id,
A3 }
AA * return render(request, 1traveler/post.html', context=context)

Next, in the models.py file, within the


get_absolute_url function, we'll correctly form the
route to the article.

def get_absolute_url(self):
return reverse('post', kwargs={'post_slug':
self. slug})

18
19 def get_absolute_url(self):
20 return reverse('post’, kwargs={1post_slug': self.slug})
21

Consequently, the 'post_slug' parameter will be


passed into urls.py, in the
path('post/slug:post_slug/', show_post,
name='post') route, forming a complete path to our
page.
So, let's go to the website, open the main page, and
click on the 'Read Post' button.
So, this article was retrieved from the database using
the 'cambodia' slug. Moreover, we made absolutely no
changes in the templates thanks to the use of the
get_absolute_url method.
Furthermore, Django protects such addresses from so-
called SQL injections that malicious users might
attempt by manipulating the browser's address bar to
execute SQL queries.
Forms not related to models

Forms are one of the most crucial elements in website


development. For instance, when we perform
authentication or registration on a website, a
corresponding page appears with input fields, lists,
checkboxes, and so forth.

HTML forms are defined using the <form>...</form>


tag and are used to transmit user information to the
server. For instance, login and password for accessing a
website.

You can find detailed information on the website


https://django.fun/ru/docs/django/4.1/topics/form
s/.

In Django, forms can be created either in conjunction


with database tables or independently. There exists a
functional difference between these two types of forms.
For instance, when we authenticate or register a user
on a website, it involves databases. In such cases, it's
advisable to use the form in conjunction with a
database model.
On the other hand, if the task involves site search or
sending an email to the user, it's evident that creating
a connection to the database is unnecessary.
Let's first consider a form not associated with a model.
We'll do this using the example of adding articles to the
database. Later on, we'll modify it and link it to a
model.
Firstly, let's start the test web server using the familiar
command.

Python manage.py runserver

We open the main page and click on 'Add article

<- -> C Q 127.0.0.1:8000

About the site Add article Feedback

Here is where we'll create our form


<- -> O (D 127.0.0.11:8000/add page/

Adding an article

Let's open the views.py file and find the function


responsible for this page. It's the function...

def addpage(request):
return HttpResponse("Adding an article ")

def addpage(request) :
return HttpResponsef’1 Adding an article ")

Let's modify it.

def addpage(request):
return render(request, 'traveler/addpage.html',
{'menu': menu, 'title': 1 Adding an article '})

Where we'll be passing the main menu and the title


{'menu': menu, 'title': 'Adding an article'}.
Next, we need to specify the addpage.html template.
Let's create it first

v M templates
v M traveler
itJ abouthtml
addpage.html
q base.html
iff index.html
■^i list_categories.html

itJ post.html

Let's display the content as follows for now

{% extends 'traveler/base.html' %}

{% block content %}
<h1>{{title}}</h1>
<p> Page content </p>
{% endblock %}

extends 'traveter/base.htmt’ %}

{% block content %}
<hl>{{titte}}</hl>
<P> The content of the article </p>
endblock %}

Extending the base template


{% extends 'traveler/base.html' %}
In the content block, simply display the header and
paragraph.
Then, in the browser, refresh, and we'll see the next
page already appearing

<- -> o • 127.0.0.1:8000/addpage/

About the site Add article Feedback

Continents

Eur°Pe Adding an article

Page content

Everything is ready for us to place the form.

In Django, there's a special class


<form>...</form>
on which child classes are built, like class
AddPostForm(forms.Form).
Let's place this class and other logic in the forms.py file,
which we'll create in the application folder.
v a traveler
> Efl migrations
> M static
> M templates
> Efl templatetags
&_init_.py

admin.py
ft apps py

forms.py
[$, models.py

tests, py
& urls.py

views, py
> EB travels
db.sqlite3
manage, py

Let's write in this file

from django import forms


from .models import *

□ from django import forms


2 Eifrom .models import *

Let's import the forms module where all the information


for creating our classes is located.
Also, let's import models that we'll need later.
Next, we'll define the form class that describes the post
addition form.

class AddPostForm(forms.Form):
title = forms.CharField(max_length=255)
slug = forms.SlugField(max_length=255)
content =
forms.CharField(widget=forms.Textarea(attrs=
{'cols': 60, 'rows': 10}))
is_published = forms.BooleanField()
cat =
forms.ModelChoiceField(queryset=Category.objects.a
ll())

Hfrom django import forms


from .models import *
3
Flclass AddPostForm(forms.Form):
title = forms.CharField(max_length=255)
slug = forms.SlugField(max_length=255)
content = forms.CharField(widget=forms.Textarea(attrs={'cols': 60, 'rows': 10}))
is_published = forms.BooleanFieldO
cat = forms.ModelChoiceField(queryset=Category.objects.all())

All these attributes represent the fields that will be


displayed on our form, on the page.
Please note that some attributes, such as time_create
and time_update, are not specified here because they
will be automatically populated. We need to allow users
to fill in only some important fields.

It's also worth noting that the is_published field is


defined as a BooleanField, which results in a checkbox
on the page, allowing us to indicate whether the entry
is published or not.

The field for selecting the category cat is created using


the ModelChoiceField class, displaying a dropdown
list where we can choose the relevant categories. This
list will be retrieved using Category.objects.all() and
will be contained within a queryset.
For more detailed information about built-in fields, you
can read at the following link
https://django.fun/ru/docs/django/4.1/ref/forms/fields/

Thus, the form is defined and it can be used in the


presentation function def addpage in the views.py file.

def addpage(request):
form = AddPostForm()
return render(request, 'traveler/addpage.html', {'form':
form, 'menu': menu, 'title': 'flofiaB^eHue cmambu'})

idef addpage(request):
form = AddPostForm()
I return render (request, 1 traveler/addpage. html1 , {'form': form, 'menu': menu, 'title': 'Addina an article '})

We create an instance of the class: form =


AddPostForm(), then pass this parameter to the
addpage.html template, for instance, 'form': form,
and, of course, import it
from .forms import AddPostForm

from .forms import AddPostForm


from .models import *

It's better to write it like this:


from .forms import *,
so you can import any forms from this file.

from .forms import *


□from .models import *
Next, open the file addpage.html and instead of the
paragraph, insert the following code.

{% extends 'traveler/base.html' %}
{% block content %}
<h1>{{title}}</h1>

<form action="{% url 'add_page' %}"


method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit"> Add </button>
</form>
{% endblock %}

{% extends 'traveler/base.html’ %}

{% block content %}
<hl>{-{title}}</hl>

<form action=”4% url ’add_page’ method=ITpostll>


{% csrf_token %}
form.as_p }}
<button type="submit"? Add </button>
</form?

{% endblock %}

action="{% url 'add_page' %}"


indicates the URL to which we should submit this form,
specifying where to send the data to the server. In this
case, we're redirecting to the same URL where our
form is displayed, essentially refreshing the page.
"method="post"" is the data transfer method.
Usually, when using forms, the post method is
specified. For instance, when we're sending login and
password, we don't want the transfer of this data to be
visible externally for security reasons.
"{% csrf_token %}" serves to protect the form from
Cross-Site Request Forgery (CSRF) attacks. These
attacks occur when a form that looks like yours is
created on a phishing site. Users might unwittingly
input genuine login and password details, which the
attackers can intercept. Django processes only those
forms where this token is specified, generating a hidden
field with a token that refreshes upon each page reload.
If the token matches, the form is processed accordingly.

"{{ form.as_p }}"


displays all form fields using HTML paragraph elements
('p').
In reality, as_p() is a function. However, when we want
this function to be called at this point, we write it
without parentheses. We're not directly calling it as a
function; rather, we're passing a reference to it, and the
template engine will execute it.
"<button type="submit">Add</button>" - this
button allows sending data to the server.
Let's check how all of this will work. Let's go to the site
and refresh the page.
Adding an article

Is published: □

Cat: |------

Add

Let's further examine this form.


When this form is initially displayed on the page, its
fields are empty.
After the user enters some data and clicks 'Submit',
this data is sent to the server, where it undergoes
validation.
If everything is okay, the user might be redirected to
another page. If errors occur, the form is displayed
again to the user.
At this point, the form should not be empty; it should
display the data previously entered by the user.
How do we achieve this in our program?
Let's demonstrate first on a diagram.

def addpage(request):
if request.method == 'POST':
form = AddPostForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
else:
form = AddPostForm()
return ...

Let's break it down now.


When the form is first sent to this page, the request
object's method property is set to NONE because we
haven't sent any data yet.
Therefore, the condition
if request.method == 'POST':
won't trigger, and we'll move to the else: line, where we
generate a standard empty form:
form = AddPostForm().
However, when the user enters all the data and clicks
the 'Add' button, the check if request.method ==
'POST':
triggers, and the form
form = AddPostForm(request.POST)
is created, containing the data filled in by the user. If
the data validation fails, the form will be displayed a
second time with the fields pre-filled.
If the validation passes, print(form.cleaned_data) will
display the sanitized data in the console.
Let's implement this functionality in our program.

27 Rdef addpage(request):
28 R if request.method == 'POST':
form = AddPostForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
else:
33 form = AddPostFormO
34 A return render(request, 'traveler/addpage.html', {'form': form, 'menu': menu, 'title': Adding an article •})

Let's check how this will work.

After clicking 'Add,' the form was saved a second time.


In the terminal...

{'title': 'Er^net', 'slug': 'Egypt', 'content': 'Egypt', ' is_publlshed ’ : True, 'cat': cCategory: Africa >}
[13/0ec/2022 11:15:23] "POST /addpage/ HTTP/1.1" 200 3564

As you can see, everything was processed correctly as


well.
Now, let's make it so that when filling out the form, we
encounter some errors.
For instance, in the 'slug' field, let's input prohibited
characters, in simpler terms, using Cyrillic.

Adding an article

Title: I Tunis |

• The value must contain only Latin letters, numbers, underscores or hyphens.

Slug: jhjhjjjjpopn ~|

We see that the framework automatically generated


this message.
Nothing was displayed in the console because the form
submission didn't pass the validation.
Let's enhance the appearance of the form a bit.
Firstly, let's change the field names to Russian.
Go to the forms.py file and add something to the
necessary fields in the AddPostForm class.

class AddPostForm(forms.Form):
title = forms.CharField(max_length=255, label=" Title ")
slug = forms.SlugField(max_length=255, label="URL")
content = forms.CharField(widget=forms.Textarea(attrs=
{'cols': 60, 'rows': 10}), label=" Content")
is_published = forms.BooleanField(label=" Publication
")
cat =
forms.ModelChoiceField(queryset=Category.objects.all(),
label=" Categories ")
Hfrom djangc import forms

2 Hfrom .models import *

4 'iclass AddPostForm(forms.Form):

title = forms.CharFleld(max_length=255, label="header11)

slog = forms.SlugField[max_leng =255, labi ="URL")

content = forms.CharFleld(widget=forms.Textarea(attrs={’cols': 6G, 'rows': IS}), la bel=" Content")


8 ? is_poblished = forms.BooleanField(tabel="Publication ")

9 i cat = forms. ModelChoiceField Cqueryset=Category. objects. allO , label="Categories")

After refreshing the page, we'll see the field names in


Russian.
Let's continue modernizing further.
is_published = forms.BooleanField(label="
Publication ", required = False , initial=True)
Thus, required = False makes this field optional, while
initial=True will pre-select the checkbox by default

cat =
forms.ModelChoiceField(queryset=Category.objects.all(),
label=" Categories ", empty_label=" Category not
selected ")
This parameter allows writing this message instead of
dashes in the field.

class AddPostFormCforms.Form):
title = forms.CharField(max_length=255, label="header")
slug = forms.SlugField(max_length=255, label="URL")
content = forms. CharField (widget=forms. Textarea (attrs={' cols 1 : 60, 'rows': 10}), la bel=" Content")
is_publlshed = forms.BocleanField (label-"Publication" f required=False, initial=True)
cat = forms.ModelChoiceFieldCqueryset=Category.objects.all(), la bel=" Categories" , empty_label= "Category not selected")

Again, to know all the parameters, please refer to the


documentation, the link to which is provided above.
Let's spruce up our form a bit.
To do this, go to the addpage.html template and
change it to

<P>
<label class="form-label" for="{{
form.title.idforlabel }}">{{ form.title.label}}:
</label>
{{ form.title }}
</p>
<div class="form-error">{{ form.title.errors }}
</div>

<P>
<label class="form-label" for="{{
form.slug.id_for_label }}">{{ form.slug.label}}:
</label>
{{ form.slug }}
</p>
<div class="form-error">{{ form.slug.errors }}
</div>

<P>
<label class="form-label" for="{{
form.content.id_for_label }}">{{ form.content.label
}}: </label>
{{ form.content}}
</p>
<div class="form-error">{{ form.content.errors }}
</div>

<P>
<label class="form-label" for="{{
form.is_published.id_for_label }}">{{
form.is_published.label}}: </label>
{{ form.is_published }}
</p>
<div class="form-error">{{
form.is_published.errors }}</div>

<P>
<label class="form-label" for="{{
form.cat.id_for_label }}">{{ form.cat.label}}:
</label>
{{ form.cat}}
</p>
<div class="form-error">{{ form.cat.errors }}
</div>

<P> Reader
<label class='Tform-label" for="{{ form.title.icLfor_label }}">{{ form, title .label }}: </label>
{{ form.title }} Q]
</p>
<div class=,Tform-error">{{ form.title.errors }}</div>

<p>
<label class=irform-label" for="{-{ form.slug.id_for_label }}'’>-{{ form, slug .label }}■: </label>
- {{ form.slug }}
</p>
<div class="form-error,r>{-{ form, slug .errors }}</div>

<p>
<label class-"fcrm-label" for-"{{ form.content.id_for_label }}">-{{ form.content.label </label>
- {{ form.content }}
</p>
<div class=,rform-error,r>{-{ form.content.errors }}</div>

<label class=,Tform-label" for="’{{ form.is_published.id_for_label }}”>{{ form.is^published.label }}: </label>


- {{ form.is_published }}
</p>
<div class="form-error">{-{ form. is_published .errors }}</div>

<label class='’form-label" for="{{ form.cat.id_for_label }}">{-{ form.cat.label }}: </label>


- {{ form.cat }}
</p>
<div class=”form-error">{-{ form.cat.errors }}</div>
Using the tag for="{{ form.title.id_for_label }}" we
set a unique identifier using the id_for_label property.
{{ form.title.label }}: allows displaying the actual
label.
{{ form.title }}: will insert the corresponding input
tag for the title.
<div class="form-error">{{ form.title.errors }}
</div>: shows potential errors when entering the title.
This utilizes the errors collection.
class="form-label": styling classes that I've
predefined and placed in the style sheet.

Let's navigate to the page and refresh.


Adding an article
Title: | |

URL: | |

Content:

Publication: □

Categories: | Category not selected v |

Add

Everything seems to be working fine.


However, it's noticeable that the amount of code has
significantly increased. This can be substantially
reduced by addressing repetitive lines and utilizing a
for loop.
{% csrf_token %}

{% for f in form %}

<p> <label class="form-label" for="{{


f.id_for_label }}">{{f.label}}: </label>{{ f }}</p>
<div class="form_error">{{ f.errors }}</div>

{% endfor %}

{% for f in form %}

<p> <label class="form-label" for="{-{ f.id_for_label }}">-{-{f .label}}: </label>-{{ f }}</p^


<div class="form_error">-[{ f.errors }}</div>

{% endfon %}

We go to the page, refresh, and see that nothing has


changed. Everything is working.

Adding an article
Title: |

URL: Q

Content:

Publication: □

Categories: I Category not selected ~y |

Add
All fields now have the same formatting styles under
class="form-label" and class="form_error," but
they can be customized if desired. Each field can have
its own style assigned to it. Let's navigate to the
forms.py file and within the AddPostForm class,
specify a special parameter called widget

class AddPostForm(forms.Form):
title = forms.CharField(max_length=255, label=" Title ",
widget=forms.TextInput(attrs={'class': 'form­
input'}))
slug = forms.SlugField(max_length=255, label="URL")
content = forms.CharField(widget=forms.Textarea(attrs=
{'cols': 60, 'rows': 10}), label="KoHTeHT")
is_published = forms.BooleanField(label=" Publication ",
required=False, initial=True)
cat =
forms.ModelChoiceField(queryset=Category.objects.all(),
label=" Categories ", empty_label=" Not selected category
")

This parameter is assigned corresponding attributes

TextInput(attrs={'class': 'form-input'}))
In this case, the line attrs={'class': 'form-input'}))
will mean that for the title...

Title:

we will be specifying our own styling class


Cinput type="text" name="title" class="form-input" maxlength="255" required
id=”id_title"> == $0

As we can see, class='form-input' has been added. To


view the code, use the code inspector in your browser.
This way, fine-tuning each field is possible when
needed.
Here's what has been achieved.
Adding an article
Title: |

URL: |

Content:

Publication: □

Categories: | Category not selected y» |

Add

Next, let's proceed with adding the data from the form
into the database.
Open views.py and add a few lines of code to the
addpage module

def addpage(request):
if request.method == 'POST1:
form = AddPostForm(request.POST)
if form.is_valid():
#print(form.cleaned_data)
try:
Dir_travel.objects.create(**form.cleaned_da
ta)
return redirect('home')
except:
form.add_error(None, 'Post addition error')
else:
form = AddPostForm()
return render(request, 'traveler/addpage.html', {'form':
form, 'menu': menu, 'title':
‘Adding an article'})

Hdef addpage(request):
E if request.method == 'POST':
form = AddPostForm(request.POST)
if form.is_valid():
#print(form.cleaned_data)
try:
Dir_travel.objects.create(**form.cleaned_data)
return redirect(1 home 1)
except:
form.add_error(None, 'Erroradding post' )
else:
form = AddPostFormO
A return render(request, 'traveler/addpage.html', {'form': form, 'menu': menu, 'title': 'Adding an article'})

In the 'try' block, we add a new entry using


Dir_travel.objects.create(**form.cleaned_data). If
the addition is successful, we redirect to the home page
using return redirect('home'). If any issues arise, we
move to 'except' and add an error to display on the
form page using form.add_error(None, Post
addition error).
#print(form.cleaned_data) - we will comment this out.
To display this error on the page, we'll add a line to the
template

{% csrf_token %}
<div class="form-error">{{ form.non_field_errors
}}</div>
{% for f in form %}

{% csrf_token %}
<div class="form-error">{{ form.non_field_errors }}</div>
{% for f in form %}

And we'll display errors not related to the fields. Now,


let's go to the form, refresh it, and fill it in with some
data.

We received an error because we already have a slug


like that in our database. This record exists in the
database, and as we recall, the slug must be unique.
Let's change the slug, for example, to Italy_1
Zimbabwe

Great, it all worked out! Although without a photo for


now, that's not crucial at the moment. Next, let's try
using forms in conjunction with a model.

Forms related to models. Custom


validators.

The previous example was somewhat artificial since


adding a new post is inevitably linked to accessing the
database in one way or another. Consequently, we've
encountered code duplication in both the forms.py and
models.py files, which isn't exemplary. Let's do the
following: navigate to the forms.py file and make the
AddPostForm class inherit from another class. Inside
this class, we'll remove all the fields and specify them
within a nested Meta class.
class AddPostForm(forms.ModelForm):
class Meta:
model = Dir_travel
fields = '__all__'

model = Dir_travel associates this class with our


model, while "fields = 'all'" indicates which fields
should be displayed in the form. It means to show all
fields except those that are filled in automatically.
Let's test how this works. Start the server, go to the
main page, and click on 'Add Article'. You'll see a form
linked to the model, with fields automatically generated
in conjunction with our model, including the 'Photo'
field.
Adding an article

Add

However, in practice, it's recommended to explicitly


specify the list of fields. Let's specify the fields
excluding photographs.
class AddPostForm(forms.ModelForm):
class Meta:
model = Dir_travel
fields = ['title', 'slug', 'content', 'is_published',
'cat']

Adding an article
Title:

Article text

Publication:

Category:

Add

Let's set up styling for the first three fields.

class AddPostForm(forms.ModelForm):
class Meta:
model = Dir_travel
fields = ['title', 'slug', 'content', 'is_published', 'cat']
widgets = {
'title': forms.TextInput(attrs={'class': 'form­
input'}),
'content': forms.Textarea(attrs={'cols': 60,
'rows': 10}),
}
□ class AddPostForm(forms.ModelForm) :
class Meta:
model = Dir_travel
fields = ['title1, 'slug', 'content', 'is_published', 'cat']
widgets = -{
'title': forms. Textinput (attrs=-{' class': 'form-input'}),
'content': forms.Textarea(attrs={'cols': 6G, 'rows': 10}),
}

The sizes have changed slightly.


Next, for the 'Category' field, instead of dashes, it
should display 'Category not selected'. Let's define a
constructor in the AddPostForm class.

class AddPostForm(forms.ModelForm):
def_ init_ (self, *args, **kwargs):
super()._ init_ (*args, **kwargs)
self.fields['cat'].empty_label = 'Category not
selected'

class Meta:
model = Dir_travel
fields = ['title', 'slug', 'content', 'is_published', 'cat']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-input'}),
'content': forms.Textarea(attrs={'cols': 60, 'rows':
10}),
}

class AddPostForm(forms.ModelForm):
def _init_ (self, *args, **kwargs):
super().__ init__ (*args, **kwargs)
self .fields ['cat'] .empty_label = ' Category not selected '

class Meta:
model = Dir_travel
fields = ['title1, 'slug', 'content', 'is_published', 'cat']
widgets = ■(
'title': forms.TextInput(attrs=-{' class': 'form-input'}),
'content': forms.Textarea(attrs={'cols': 6G, 'rows': 10}),
}

When an instance of the form class AddPostForm is


created, the constructor is invoked: def init(self,
*args, **kwargs):, which in turn should call the
constructor of the base class super().init(*args,
**kwargs) to execute automatic actions, specifically to
access the fields property with the key 'cat', and only
after that, we modify the empty_label property for the
'cat' field.

Category: | Category not selected v

Inside this form, a method called save has appeared,


allowing the form data to be directly saved into the
database.
Moving to the views.py file, instead of the line
Dir_travel.objects.create(**form.cleaned_data)
within the addpage module,
let's simply use form.save().
As a result, all data submitted in the form will be
automatically added to the database. Moreover, this
method no longer needs to be placed within a
try/except block, as it inherently performs all necessary
checks.
Therefore, this block can be removed, simplifying the
code even further. In the end, it will look like this:

def addpage(request):
if request.method == 'POST1:
form = AddPostForm(request.POST)
if form.is_valid():
form.save()
return redirect('home')

else:
form = AddPostForm()
return render(request, 'traveler/addpage.html', {'form':
form, 'menu': menu, 'title': ‘Adding an article'})

27 i def addpage(request):
28 i-i if request.method == 'POST':
form - AddPostFormCrequest.POST)
if form.is_validC):
#print(form.cleaned_data)
32 form.saveC)
return redirect('home')

else:
form = AddPostFormO
37 El return renderfrequest, ' traveler/addpage . html', {'form': form, 'menu': menu, 'title': ' Addino an article 1})

Let's go to the browser, click on 'Add Article,' and fill


out the form. Specifically input a non-unique slug in the
URL field, one that already exists, to check the
message Django will provide us.
Adding an article

Indeed, we received a specific message indicating that


such a URL already exists.
It means the save method automatically generated a
message related to that specific issue, which is quite
practical.
Let's still add a new post. The article text isn't
important for now.

Continent: Europe

Spain
GHGCDHVSDH SHCDJ SDCJBSJHDBC

That's great! You've got a new post.


If you click on the 'Read Post' link, you'll navigate to
the page displaying that post. In the browser's address
bar, you'll also see the unique slug associated with it.
127.0.0.1:8000/post/spain/

Add article Feedback

Spain
ghgcdhvsdh shedj sdej b sj hd be

From this point, let's focus on the 'Photo' field, which


was previously ignored to avoid overwhelming
attention. We'll work with forms associated with
models.
In the forms.py file, within the AddPostForm class,
within the Meta subclass, let's add this field

class AddPostForm(forms.ModelForm):
def_ init_ (self, *args, **kwargs):
super()._ init_ (*args, **kwargs)
self.fields['cat'].empty_label = 'Category not selected'

class Meta:
model = Dir_travel
fields = ['title', 'slug', 'content', 'photo', 'is_published',
'cat']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-input'}),
'content': forms.Textarea(attrs={'cols': 60, 'rows':
10}),
}

fictass AddPostForm(forms.ModelForm):
def _init_ (self, *args, **kwargs):
super(). _init (*args, **kwargs)
self .fields [ 'cat' ] .empty_label = ' Category not selected '

class Meta:
model = Dir_travet
fields = ['title', 'slug', 'content', 'photo', 'is_published', 'cat']
widgets = {
13 T 'title': forms.Textlnput(attrs={'class': 'form-input'}),
'content': forms.Textarea(attrs=-{'cots1: 60, 'rows': ID}),

Next, open the views.py file, and within the addpage


function, add the line
form = AddPostForm(request.POST,
request.FILES),
where, when creating an instance of the AddPostForm
form, we pass a list of files sent from the form to the
server via request.FILES as the second argument.
This means accessing the request object and retrieving
its FILES collection.

def addpage(request):
if request.method == 'POST1:
form = AddPostForm(request.POST, request.FILES)
if form.is_valid():
#print(form.cleaned_data)
form.save()
return redirect('home')

else:
form = AddPostForm()
return render(request, 'traveler/addpage.html', {'form':
form, 'menu': menu, 'title': ‘Adding an article'})
jidef addpage(request):
ji if request.method == 'POST':
form - AddPostForm(request.POST, request.FILES)
if form.is_valid():
#print(form.cleaned_data)
form.save()
• return redirect('home')

else:
form = AddPostFormO
•1 return render(request, 'traveler/addpage.html', {'form': form, 'menu': menu, 'title': 'Adding an article '})

Finally, in the addpage.html file, let's add the line


<form action="{% url 'add_page' %}'" method="post"
enctype="multipart/form-data'">

<form action="-(% url 'add_page' %}" method="post" enctype="multipart/form-data">


{% csrf_token %}
<div class="form-error">{{ form.non_field_errors }-}</div>
■{% for f in form %}

If we're submitting any files along with the form data,


it's essential to specify the attribute
enctype='multipart/form-data'.
Let's navigate to the browser, refresh the page, and
post something, but this time with an uploaded photo.
Yes, everything worked out.
Let's click on 'Read Post,' and here too, everything
worked
© 127.0.0.1:8000/post/netherlands/

Ad d a rtic le Feed ba ck

Netherlands
The Netherlands (Dutch: Nederland [ ne:dar
Caribbean. It is the largest of four constituen
Germany to the east, and Belgium to the sou
Belgium in the North Sea.[18] The country’s
Saxon and Limburgish are recognised region
English and Papiamento are official in the C;

The four largest cities in the Netherlands are


[20] The Hague holds the seat of the States General. Cabinet and Supreme Court.[2
busiest in Europe. The Netherlands is a founding member of the European Union, E
several intergovernmental organisations and international courts, many of which an

Netherlands literally means "lower countries" in reference to its low elevation and 1
level. [24] Most of the areas below sea level, known as polders, are the result of lane
unique era of political, economic, and cultural greatness, ranked among the most pc
trading companies, the Dutch East India Company and the Dutch West Lidia Comp;

With a population of 17.7 million people, all living within a total area of 41,850 km
country in the world and the second-most densely populated country in the Europe?
largest exporter of food and agricultural products by value, owing to its fertile soil.

If we navigate to the project, we'll see that our image


has been uploaded where it should be.
Django took care of all the photo upload operations -
placing it on the server, creating a path to it, and
storing that path in the database. We didn't have to do
all of this manually.
ill
Validation of form fields
Let's consider the validation of form fields. Currently,
the correctness check of fields is executed according to
the Dir_travel model. For example:
max_length=255 - maximum of 255 characters.
unique=True - field must be unique.
blank=True - field is not mandatory.
All these parameters that we specify participate in the
validation process. However, there are nuances. For
instance, when working with the SQLite database, the
condition max_length=255 doesn't trigger. This is a
peculiarity of this DBMS.
Often, standard checks are insufficient, and there's a
need to create a custom validator at the form level.
Let's create such a validator for the 'title' field, which
every time the length of the field exceeds 200
characters, will display an appropriate message.
The process of verifying the data's correctness occurs
according to the following principle. When the user fills
in all the form fields and clicks the 'Submit' button, the
data goes to the server, where it's checked using the
methods is_valid() and save(). Initially, the standard
validators are executed, and only when they succeed,
the user-defined validators are activated.
We'll create one such user-defined validator. To do this,
open the forms.py file and within the AddPostForm
class, create a method that should start like this:
def clean_ ... then comes the field, in this case, 'title'.
This is the field upon which we'll perform the validation.
Therefore, the complete method will look like this:

def clean_title(self):
So, let's write down

def clean_title(self):
title = self.cleaned_data['title']
if len(title) > 200:
raise ValidationError(‘The length exceeds 200
characters')
return title

def clean_title(self):
title = self.cleaned_data['title']
19 t if len(title) > 200:
raise ValidationError('Lengthexceeds200characters ')
return title

title = self.cleaned_data['title'] - retrieves data for


the title, where we access the cleaned_data collection
available in the instance of the AddPostForm class.
Then, using the key ['title'], we obtain the title entered
by the user. If the length of this title is greater than 200
characters (if len(title) > 200:), it raises a
ValidationError exception. Otherwise, it returns the title
(return title).

In order to use the ValidationError class, we need to


import it.
from django.core.exceptions import ValidationError

from django import forms


from .models import *
from django.core.exceptions import ValidationError
Let's check how this will work. Right now, it doesn't
matter what exactly we're adding. The main thing is to
input more than 200 characters in the 'Title' field

Adding an article
Title: | dfg fdddddddddddddddddddg dfgddddddd» |

• Length exceeds 200 characters

Article text:

Photo: Choose I-lie | File not selected

Publication: □
Category: | Ash

Add

We see the expected message, indicating that our


validator has been triggered. Similarly, of course, we
can define a validator for any other field.
View classes.
Let's consider how we can use view classes instead of
functions. Previously, we used functions, which is a way
to implement simple functionality.
Let's move on to a somewhat different and more
advanced way of implementing our functionality—using
classes. View classes in English are denoted as CBV -
Class-Based Views. Object-oriented programming, as
known, notably simplifies code writing when applied to
classes.
For more detailed information about class-based views,
you can read by following this link: Django Class­
Based Views Documentation
At this moment, we'll replace the 'index' function,
responsible for processing the main page, with a view
class.
After reading the documentation page on classes, we
understand that the 'ListView' class would be most
suitable for our purposes. This class creates a list of
something.
To start, let's import this class.
from django.views.generic import ListView

from django.views.generic import ListView

Next, let's create a class that will handle the main page
and name it
Dir_travelHome.

class Dir_travelHome(ListView):
model = Dir_travel
Rclass Dir_travetHome(ListView):
model = Dir_travet

Our class will be based on the base class 'ListView'.


model = Dir_travel - the 'model' attribute refers to
the 'Dir_travel' model associated with this list of
articles. This statement model = Dir_travel selects all
records from the list and attempts to display them as a
list.

By default, the 'ListView' class uses the following


template:
<app_name>/<model_name>_list.html
Next, let's connect this in our route, in the 'urls.py' file.
Instead of 'index' in the line below
Dir_travelHome - path('', Dir_travelHome,
name='home'),
To link the route to this class, you need to call a special
function 'as_view()':

path('', Dir_travelHome.as_view(), name='home'),

We're not just giving a reference to this function; we're


specifically calling it.

Let's start our server. And we have an error!

ImportError: cannot import name 'index' from 'traveler.views1 (C:\Python\Django\travels\travels\traveler\views.py)


£_______________________________________________________________________________________________________________________________________
The issue is that we haven't created the template that
is searched for by default at this address:
'traveler/Dir_travel_list.html'.
That means we should create a template at this
address. However, we won't do that because we
already have a template named index.html.

Let's specify this in our class.

class Dir_travelHome(ListView):
model = Dir_travel
templatename = 'traveler/index.html'
3class Dir_travelHome(ListView):
model - Dir_travel
17 ©t 0 template_name = ’travelen/index.html’

And again, there's an error in the console

It's referring to the same 'index' again.


Let's go to the 'urls.py' file using the path
'travels\urls.py' and remove the reference to the
module that no longer exists

from django.contrib import admin


from django.urls import path, include
from traveler.views import index
from django.contrib import admin
from django.urts import path, include
from traveler.views import index

Let's start the server again


<- -> C 0 127.0.0.1:8000

Continents

Europe

Asia

The page appeared but without posts and the menu.


The issue is that in the 'index.html' file, the line
{% for p in posts %} uses 'posts,' whereas the
'Dir_travelHome' class forms another collection
named 'objectjist.' Let's change 'posts' to
'objectjist'

{% for p in objectjist %}

5 for p in object_list
<lixdiv class="article-panel">

Now we see all our articles. However, if we want to use


the 'posts' variable in the template, we also need to
specify it explicitly in the view class.

class Dir_travelHome(ListView):
model = Dir_travel
template_name = 'traveler/index.html'
context_object_name = 'posts'
Rclass Dir_travelHome(ListView):
17 Of model = Dir_travel
18 Of template_name = ’traveler/index.html ’
19 Of 0 context_object_name = 'posts’

Let's go to the browser and refresh the page.


Everything should still work as before.

Missing the main header and the top horizontal menu.


Let's add the title 'Home Page'.
We'll indicate the easiest way. Let's add one more line
to our class.

class Dir_travelHome(ListView):
model = Dir_travel
template_name = 'traveler/index.html'
context_object_name = 'posts'
extra_context = {'title': 'Home Page'}

class Dir_travetHome(ListView):
model = Din_travel
template_name = ’tpavelen/index.html
context_object_name = 'posts’
extra_context = {’title’: 'Main page’}

But with this parameter, only static, unchangeable data


can be passed. In other words, lists, tuples, collections
cannot be passed.
Let's update the home page and check the tab.

- Main page

<- -> O (D 127.0.0.1:8000

Our main horizontal menu is already represented as a


dynamic list.
Let's create a function that will generate both static and
dynamic contexts to be passed into the template.
Alternatively, we need to pass not only all other
contexts but also the list

menu = [{'title': " About the site ", 'url name':


'about'},
{'title': " Add article ", 'url_name': 'add_page'},
{'title': " Feedback ", 'url_name': 'contact'},
{'title': " Sign in ", 'url_name': 'login'}

We'll define this function in our class

class Dir_travelHome(ListView):
model = Dir_travel
template_name = 'traveler/index.html'
context_object_name = 'posts'
extra_context = {'title': 'Home Page'}

def get_context_data(self, *, object_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
context['menu'] = menu
return context

nclass Dir_travelHome(ListView):
17 Of model = Dir_travel
18 Of template_name = 'traveler/index.html'
19 Of context_object_name = 'posts'
20 Of extra_context = -{'title': 'Main page1}

def get_context_data(self, *, object_list=None, **kwargs):


context = super().get_context_data(**kwargs)
context!'menu'] = menu
25 0 T return context

First, let's obtain the context that is already formatted


for our template.
context = super().get_context_data(**kwargs)
For instance, 'posts' already exists. We cannot
overwrite this collection.
super() - refers to the base ListView class and retrieves
the existing context
get_context_data(**kwargs) - retrieves all named
parameters.
Next, we pass the named menu into this context using
the key
context['menu'] = menu,
assigning the menu list to this key.
Finally, we return this context using return context.
Once again, this function dynamically generates the
existing context and adds the menu list to it.

Let's update the home page.

But that's not all.


The 'Continents' tab should be selected.
Let's add one more line to our class.

def get_context_data(self, *, object_list=None, **kwargs):


context = super().get_context_data(**kwargs)
context['menu'] = menu
context['title'] = 'Home Page'
context['cat_selected'] = 0
return context

Since we're passing the context in this function, let's


remove the line above:
extra_context = {'title': ‘Home Page’}
and instead, within the function itself, let's use
context['title'] = ‘Home Page’.

nclass Dir.travelHome(ListView):
17 Gf model = Dir_travel
18 Gt template_name = 'traveler/index.html'
19 Gt context_object_name = ’posts'

def get_context_data(self, *, object_list=None, **kwargs):


context = super().get_context_data(**kwargs)
24 context['menu'] = menu
25 context ['title ' ] = ' Mainpaqe'
26 context['cat_selected'] = 0
27 return context

Let's refresh/update the page.


Continents

Europe

Asia

Africa

South America

Let's make it so that on the home page, we have all the


articles marked for publication. Right now, we have all
the articles.
We'll use a specific method

def get_queryset(self):
return
Dir_travel.objects. filter(is_published=True)

We return from our model only those records where...


is_published=True.
def get_queryset(self):
return Dir_travel.objects.filterCis_published=True)

To test the functionality of this method, let's go to the


admin panel in the browser and uncheck the
checkboxes in the 'Publication' field for some articles.

□ - 2 A TIME OF CREATION 1 F’H0T0 PUBU CATION

□ 16 Netherlands December 1B.2022 12:19pm photos/2022/12/18/Nede rland.j pg □


□ 13 Tanzania December 2. 2022 12:00 pm photos/2022/12/02/tanzania.jpg □
□ 12 Zimbabwe December 2, 2022 11:58 am photos/2022/12/02/zi mbabwe.j pg □
□ 11 Cambodia December 2. 2022 11;57am phOtOS/2022/12/02/CambOdia.jpg □
□ 10 China December2, 2022 11:55 am photos/2022/12/02/china.jpg □
However, upon exiting the admin panel and returning
to the home page, those articles for which we
unchecked the boxes will disappear. Just remember to
save this action in the admin panel.

To display the list by separate categories, let's create a


similar class instead of a function.

class Dir_travelCategory(ListView):
model = Dir_travel
templatename = 'traveler/index.html'
context_object_name = 'posts'

def get_queryset(self):
return
Dir_travel.objects. filter(cat_ slug=self.kwargs[cat_sl
ug], is_published=True)
;lclass Dir_travelCategory(ListView):
54 ©1 model = Dir_travel
55 ©t template_name = 'tnaveler/lndex.html’
56 ©t context_object_name = 'posts'
57
58 ©T def get_queryset(self):
59 return Dir_travel.objects,filter(cat__slug=self.kwargsf'cat_slug'], is_published=True)

In the method def get_queryset(self):, we select


records from the Dir_travel table only those that
correspond to the category specified by the slug
'cat_slug'.
Remember, in our urls.py, we've indicated an identifier.
However, we want to specify a slug.

urlpatterns = [
path(", Dir_travelHome.as_view(), name^home'),
path( about/, about, name='about'),
path( addpage/, addpage, name='add_page'),
path( 1 contact/, addpage, name= 1 contact'),
path('login/, login, name='login'),
path( 1 post/<slug:post_slug>/, show_post, name=post'),
path('category/<int:catjd>/, show_category,
name='category'),
]

We'll change the last entry to:


Furthermore, through the parameter
kwargs['cat_slug'], we can retrieve all the
parameters of our route. That is, when an instance of
the Dir_travelCategory class is formed for a specific
request, through the link self

return
Dir_travel.objects.filter(cat__slug=self.kwargs['ca
t_slug'], is_published=True)

we access the kwargs dictionary and refer to the


constant 'cat_slug'.
And now, we should select from the Dir_travelCategory
table all records for which the 'cat_slug' coincides with
the slug of categories cat__slug
return
Dir_travel.objects.filter(cat__slug=self.kwargs['ca
t_slug'], is_published=True)
cat__slug means that for the cat object in the
Dir_travel model, there is a reference to the Category
model.
cat = models.ForeignKey('Category',
on_delete=models.PROTECT, null=True,
verbose_name="Category")
and we refer to the slug field of the categories table
cat.
Then, in urls.py, let's change
path('category/slug:cat_slug/',
Dir_travelCategory.as_view(), name='category'),

and finally, in models.py, let's change

def get_absolute_url(self):
return reverse('category', kwargs={'cat_slug':
self.slug})

We'll refresh/update the page


The title, main horizontal menu, and the left sidebar
highlighting the category we are currently in aren't
displaying. Let's fix that.

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 1 Category - 1 +
str(context['posts'][0].cat)
context[menu'] = menu
context['cat_selected'] = context['posts']
[0].cat_id
return context
def get_context_data(self, *, oblect_list=None, **kwargs):
context = super().get_context_data(**kwargs)
context['title 1 ] = 'Category - ' + str(context['posts'][Q].cat)
context['menu'] = menu
context['cat_selected1] = context['posts'][0].cat_id
return context

context = super().get_context_data(**kwargs) -
We form the data context that is already created by the
base ListView class.
context['title'] = 'Category - ' +
str(context['posts'][0].cat)
In this line, posts represent the collection of read
records, where we take the first record [0] and access
the 'cat' attribute, which returns the category's name.
We convert all of this into a string using str and add it
to the string 'Category - '.
context['cat_selected'] = context['posts']
[0].cat_id - We do the same, but this time we fetch the
identifier of the selected category, cat_id.
And we return return context.
Let's check how this will work.
It seems everything is working as intended. If you
navigate to the 'Europe' category, you'll notice that
everything works just as planned.
t Category Europe

Continents

Europe Continent: Europe

Asia
Italy
Africa ITALY (ITALIAN: IT;

South America
However, there is one nuance. Let's type in the
browser's address bar a non-existing slug, for example

http://127.0.0.1:8000/category/aziya5/
y Hac B03HUKHem oi±iu6ka
-> C © 127.0.0.1:8000/category/aziya5/

IndexError at /category/aziya5/
list index out of range
Request Method: GET
Request URL: http://127.0.0.1:8000/category/aziya5/
Django Version: 4.1.1
Exception Type: IndexError
Exception Value: list index out of range

Exception Location: C:\Python\Django\travels\venv\lib\site-packages\django\db\niodels'query.py, line 446. in__ qetitem


Raised during: traveler.views.DirJravelCategory
Python Executable: C:\Python\Django\travels\venv\Scripts\python.exe
Python Version: 3 10.7
Python Path: l’c :\\Pythcxi\\Django\\travels\\travels',
’C:\\Pyt honWpython310.zip’t
’ C: \\Pyt hon \ \ DL Ls ’,
’C:\\Python\\lib’,
’C:\\Pythcxi’,
’ C: \\Pyt hon\\0j ango\\t ravels\\venv ’ t
’C:\\Python\\Django\\travels\\venv\\lib\\site-packages’]

Server time: Sun, 25 Dec 2022 10:48:15 +0000

Traceback Switch to copy-and-paste view


C:\Python\Django\travels\venv\lib\site-packages\django\core\handlers\exception.py, line 55, in inner

55. response = get_response(request)

Written
Exception Value: list index out of range

This occurs because if we don't have any existing


articles (Posts), we can't access the first entry using
context['posts'][0].cat_id since it doesn't exist.
If there are no entries in our list, let's generate a 404
error.
To do this, we'll add the following attribute
class Dir_travelCategory(ListView):
model = Dir_travel
template_name = 'traveler/index.html'
context_object_name = 'posts'
allow_empty = False
class Dir_travelCategory(ListView):
model = Dir_travel
template_name = ’traveler/index.html ’
context_object_name = 'posts’
allow_empty = False

We'll refresh the page and see that when the page
doesn't exist, a 404 error occurs.

<- -> O O 127.0.0.1:8000/category/aziya 5/

Page not found («O4)

CniicoK nycT. ho‘Dir travelCategory.allaw empty” BbicTaeneH


Request Method: GET
Request URL: http:ll127.0.0.1:8000/category/aziya5f
Ra i sed by: traveler.views.0 ir_travel Category

Using the URLconf defined in Travels.mis, Django tried these URL patterns, in this order:

Continuing our exploration of class capabilities, let's


look at another class called DetailView, responsible for
displaying a specific post.
First, let's import it.

from django.views.generic import ListView, DetailView


from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.http import Http4G4
from django.views.generic import ListView, DetailView

Then, let's find the function responsible for displaying a


specific post, delete all its content, and write a class.

class ShowPost(DetailView):
model = Dir_travel
template name =' traveler/post.html'

Rclass ShowPost(DetailView):
Of model = Dir_travel
Of A template_name = 'traveler/post.html'

All lines are repeated from the previous example, so


everything should be clear.
Next, let's move to the list of routes in urls.py and
change the name from the function to the class

path('post/<slug:post_slug>/', ShowPost.as_view(),
name='post'),

path('post/<slug:post_slug>/1, ShowPost.as_view(), name=1 post'),


T path('category/<:slug:cat_stug>/1, Dir_traveLCategory .as_view(), name=1 category1),

Let's see how it works by going to the home page and


refreshing it.
Attributesrror at /post/italy/
Generic detail view ShowPost must be called with either an object pk or a slug in the URLconf,
Request Method: GET
Request URL: http://127.0 0_1:8000/posVitaly/
Django Version: 4.1.1
Exception Type: AttributeError
Exception Value: Generic detail view ShowPost must be called with either an object pk or a slug in the URLconf.

Exception Location: C:\Python\Djangottravels\venv\lib\site-packages\django\views\generic\detail py. line 46. in get_object


Raised during: traveler.views.ShowPost
Python Executable: C:\Python\Django\travels\venv\Scripts\python.exe
Python Version: 3.10.7
Python Path: [’C:\\Pytlion\\Ojango\\travels\\travels',
’C:\\Python\\python310.zip’,
*C:\\Python\\DLLs ’,
’C:\\Python\\lib\
’C:\\Python’,
’C:\\Python\\Django\\travels\\venv’,
’C:\\Python\\Django\\travels\\venv\\lib\\site-packages’]
Server time: Mon, 26 Dec 2022 15:30:42 +0000

Let's change in the line


path('post/slug:post_slug/', ShowPost.as_view(),
name='post'),
post_slug
to just slug
and refresh the page.
Everything should work, but without displaying posts
and the horizontal menu.
This means that this variable is used by default to
obtain the slug.
But what if we want to keep everything as it is, without
changing the variable?!
To do this, we'll apply a special attribute in our class.

class ShowPost(DetailView):
model = Dir_travel
template_name = 'traveler/post.html'
slug_url_kwarg = 'postslug'
Rclass ShowPost(DetailView):
model = Dir_travel
66 ©T template_name = ’traveler/post.html’
67 ©T A slug_url^kwarg = ’post_slug’

Let's refresh the page, and no errors appear. Let's move


on. Since the post.html template uses {{post.title}}
in the tag, we need to specify this explicitly in our class.
class ShowPost(DetailView):
model = Dir_travel
template_name = 'traveler/post.html'
slug_url_kwarg = 'post_slug'
context_object_name = 'post'
Rclass ShowPost(DetailView):
65 ©f model = Dir_travel
66 ©f template_name - 'traveler/post.html'
slug_url_kwarg = ’post_slug’
68 ©f A context_object_name = 'post'

Upon refreshing the page, the entire article with the


photograph is already displayed. Now, we just need to
pass the menu list and the tab title.

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
context['title'] = context['post']
context['menu'] = menu
return context
70 ©T n def get_context_data(self, *, oblect_list=None, **kwargs):
71 context = super!).get_context_data(**kwargs)
72 context!'title1] = context!'post']
context!'menu1] = menu
74 i return context

Let's refresh the page, and we'll have a fully functional


page ready with all the components.
Germany X

Continents

Europe
Germany
Asia Germany (German: Deutschland, j
populous country in Europe after I
Africa and the Alps to the south; it covers
borders Demnark to the north, Pol;
to the west. The nation's capital an
South America
Various Germanic tribes have inha
North America 962, the Kingdom of Germany for.
Reformation. Following the Napol

Formal unification of Germany into the modern nation-state was commer


Our channel
transformed in 1871 into the German Empire. After World War I and the '
power in 1933 led to the establishment of a totalitarian dictatorship, Worl
O YouTube organized into two separate polities with limited sovereignity: the Federa
continued its Four Power status. The Federal Republic of Germany was a
communist Eastern Bloc state and member of the Warsaw Pact. After the
—becoming a federal parliamentary republic.

Let's consider another class for working with forms. To


start, let's import the CreateView class.

from django.http import HttpResponse


from django.shortcuts import render, redirect,
get_object_or_404
from django.http import Http404
from django.views.generic import ListView, DetailView,
CreateView

J base.html views.py forms.py X addpage.html X urls.py J index.html

Rfrom django.http import HttpResponse


from django.shortcuts import render, redirect, get_object_or_404
from django.http import Http404
from django.views.generic import ListView, DetailView, CreateView

Next, to add a new post, let's create our own class


based on CreateView. Let's comment out the addpage
function and instead create a class called AddPage.

class AddPage(CreateView):
form_class = AddPostForm
templatename = 'traveler/addpage.html'

Then, in the urls.py file, we associate it with a route


path('addpage/', AddPage.as_view(), name='add_page'),

urtpatterns = [
path('', Dir_traveLHome.as_view(), name=1 home'),
pathC1 about/r, about, name-'about1),
pathC'addpage/1, AddPage.as_view(), name='add_page'),

To display the title and horizontal menu, let's add a


method get_context_data similar to the previous
classes
class AddPage(CreateView):
form_class = AddPostForm
template_name = 'traveler/addpage.html'

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
context['title'] = Add Article’
context['menu'] = menu
return context

□class AddPage(CreateView):
37 0| form_class = AddPostForm
38 O| template_name = 'traveler/addpage.html'
39
def get_context_data(self, *, oblect_list=None, **kwargs):
context = super().get_context_data(**kwargs)
context ['title ' ] = 1 Adding an article '
context['menu'] = menu
return context

Let's go to the browser, refresh the page, and try


adding a new article.
Looks like the article has been added!
Compare the code in the view function with the view
class, and it's easy to notice that the code has become
noticeably smaller and simpler. Some code is
duplicated in all our classes, and later we'll compose
them using mixins.
After adding a post, Django redirects using the
get_absolute_url method in the models.py file. If for
any reason this method is not defined, in the AddPage
class, you just need to add one line:
success_url = reversejazy('home')
Using the reverse_lazy method, we redirect to the
home page.
And don't forget to import it.
from django.urls import reverse_lazy
Main commands of Django ORM
Let's consider the main ORM commands with related
tables. As an example, let's consider the previously
created models Dir_travel and Category, which are
linked by a foreign key field cat_id.
With such rich functionality in Django's ORM, the need
to resort to SQL queries will be rare. This system also
allows querying any database type - SQLite, MySQL,
PostgreSQL, Oracle - without being dependent on the
database type.
For more detailed information, you can refer to the link
https://django.fun/ru/docs/django-orm-
cookbook/2.0/.
Next, let's consider QuerySet methods - a list of
objects of a specific model. QuerySet enables reading
data from the database, filtering, and changing their
order.
To demonstrate Django's ORM functionality, let's
move to the terminal and start the Django shell using
the command...
Python manage.py shell

(venv) PS C:\Python\DJango\travels> cd travels


(venv) PS C:\Python\Django\travels\travels> Python manage.py shell
Python 3.10.7 (tags/v3.10.7:6cc6bl3, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on Win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
Next, let's import all our models that we'll be working
with.
from traveler.models import *
Let's check if everything is working correctly by
selecting all entries from the Dir_travel table, using
the command...
Dir_travel.objects.all()

>>> from traveler.models import *


»> Dir_travel.objects.all()
<QuerySet [<Dir_travel: Tunisia>, <Dir_travel: Tanzania?, <Dir_travel: Netherlands?, <Dir_travel: Tanzania?, <Dir_travel: Zimbabwe?, <Dir_travel: Cambodia?, <Dir_travel: China?, <Dir_t
ravel: Canada?, <Dir_travel: United States?, <Dir_travel: Mexico?, <Dir_travel: Peru?, <Dir_travel: Brazil?, <Dir_travel: Italy?, <Dir_travel: Germany?, <Dir_travel: Poland?, <Dir_trav
el: Ukraine?]?

All entries are sorted as defined in the Meta class

class Meta:
verbose_name = ’'All countries 11
verbose_name_plural = 11 All countries 11
ordering ~ [1 -time_create ', 'title']

Line '27'
ordering = ['-time_create', 'title']

If not all entries are needed but only some, this can be
done using slice syntax. Let's select the first 3 entries.
Dir_travel.objects.all()[:3]

??? Dir_travel.objects.all()[:3]
<QuerySet [<Dir_travel: Tunisia?, <Dir_travel: Tanzania?, <Dir_travel: Netherlands?]?

All these queries are executed not in Python but on the


SQL query level. This can be confirmed further. First,
let's import the following module
from django.db import connection
and refer to the next collection
connection.queries

»> from django.db import connection


??? connection.queries
[-('sq!': 'SELECT "traveler.dir.travel". "id", "traveler.dir.travel". "title", "traveler_dir_travel" ."slug", "traveler.dir.travel"."content", "traveler_dir_travel". "photo", "traveler_dir_
travel"."time_create", "traveler_dir_travel"."time_update", "traveler_dir_travel"."is_published", "traveler_dir_travel"."cat_id" FROM "traveler_dir_travel" ORDER BY "traveler_dir_trave
1"."time_create" DESC, "traveler.dir.travel"."title" ASC LIMIT 21', 'time': '0.000'}, {'sq!': 'SELECT "traveler.dir.travel"."id", "traveler.dir.travel"."title", "traveler_dir_travel"."
slug", "traveler.dir.travel"."content", "traveler.dir.travel"."photo", "traveler.dir.travel"."time.create", "traveler.dir.travel"."time.update", "traveler.dir.travel"."is.published", "
traveler.dir.travel"."cat.id" FROM "traveler.dir.travel" ORDER BY "traveler.dir.travel"."time.create" DESC, "traveler.dir.travel"."title" ASC LIMIT 3', 'time': '0.000'}]

At the end of the entry, we see...


LIMIT 3’,

Yes, the first three entries were selected from the


database
Dir_travel.objects.all()[2:5]

»> Dlr.travel.objects.all()[2:5]
<QuerySet [<Dir_travel: Netherlands?, <Dir_travel: Tanzania?, <Dir_travel: Zimbabwe?]?

LIMIT 3 OFFSET 2’ ,

Let's change the order of sorting the entries based on a


specific field, for instance, the primary key (pk).
Dir_travel.objects.order_by('pk')

»> Dir.travel.objects.order.byi'pk')
<QuerySet [<Dir_travel: Ukraine?, <Dir_travel: Poland?, <Dir_travel: Germany?, <Dir_travel: Italy?, <Dir_travel: Brazil?, <Dir_travel: Peru?, <Oir_travel: Mexico?, <Dir_travel: United
States?, <Dir_travel: Canada?, <Dir_travel: China?, <Dir_travel: Cambodia?, <Dir_travel: Zimbabwe?, <Dir_travel: Tanzania?, <Dir_travel: Netherlands?, <Dir_travel: Tanzania?, <Dir_trav
el: Tunisia?]?

As we can see, all entries are arranged according to this


field. Let's reverse the order completely
Dir_travel.objects.order_by('-pk')
Or using the reverse method
Dir_travel.objects.reverse()
?>? Dir_travel.objects. reverseC)
<QuerySet [<Dir_travel: Ukraine?, <Dir_travet: Polai
States?, <Dir_travel: Canada?, <Dir_travel: China?,
el: Tunisia?]?
??? Dir_travel.objects.order_by(’pk’)
<QuerySet [<Dir_travel: Ukraine?, <Dir_travel: Polai
States?, <Dir_travel: Canada?, <Dir_travel: China?,
el: Tunisia?]?
??? 0ir_travel.objects.order_by(1 - pk1)
<QuerySet [<Dir_travel: Tunisia?, <Dir_travel: Tanz;
ravel: Canada?, <Dir_travel: United States?, <Dir_ti
el: Ukraine?]?
>» o

Next, let's observe the practical application of the filter


method, for example, selecting multiple entries based
on specific conditions.
Dir_travel.objects. filter(pk_ lte=2)
If for any reason you've closed the terminal, it's
necessary to repeat the commands we previously
entered, namely entering the Django terminal and
importing all models.

(venv) PS C:\Python\Django\travels\travels> python manage.py shate


Unknown command: 'shale'. Did you mean shell?
Type 'manage.py help' for usage.
(venv) PS C:\Python\Django\travels\travels? python manage.py shell
Python 3.10.7 (tags/v3.10.7:6cc6bl3, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on Win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from traveler.models import *

Keyboardinterrupt
»> from traveler.models import*
And after that, let's repeat entering the command
Dir_travel.objects. filter(pk_ lte=2)
>» Dir_travet.objects.fitter(pk_ tte=2)
<QuerySet [<Dir_travet: Potand>, <Dir_travet: Ukraine>]>
»> n

Using this filter, we select all records where the primary


key is less than or equal to '2'.
pk__lte=2
If you need to select a single record, then use the 'get'
method
Dir_travel.objects.get(pk =2)
And it uses the unique field by which this record is
selected. It's important to note that when using the
'filter' method, we get back a QuerySet collection,
whereas with the 'get' method, we receive an instance
of our model.
Let's see how processing of related data from different
tables occurs. First, let's select one record.
w = Dir_travel.objects.get(pk =1)
Then, the variable 'w' will reference this record. In this
variable, we have access to the following properties:
All fields that exist in our model, specifically from
w.title to w.is_published.
We can refer to the record's identifier using w.pk or
w.id.
Access the category identifier through the property
w.cat_id.
Access the Category model class property using w.cat
with the corresponding value id = cat_id.
For example, let's execute the command w.cat
<Category: EurOpe >
»> n

Indeed, we see a reference to the category.


Next, we can access any property of the Category
table, for instance, w.cat.name
»> w.cat.name
'Euraoe 1
»> [ '

Note that if we refer to a field we hadn't previously


selected, Django will execute an extra sQl query to
retrieve its value.
Next, we'll craft a query using the primary Category
model to obtain all associated posts. For instance, let's
choose the 'Europe' category and all records linked to
it in the Dir_travel model. For this, Django
automatically creates a special property for any
primary model:
<secondary_model>_set
This property enables us to select all associated
records. Let's illustrate this with an example. First, let's
select the first category from the Category model
c = Category.objects.get(pk=1)
And then we'll output 'c'

(venv) PS C:\Python\Django\travels\travels> Python manage.py shell


Python 3.18.7 (tags/v3.10.7:6cc6bl3, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on Win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
»> from traveler.models import *
»> c = Category.objects.get(pk=l)
>>> c
<Category: Europe >
Using the mechanism of reverse linking, let's read all
posts associated with this category from the Dir_travel
model.
And then the command...
c.dir_travel_set
Please note that the name of our model is written in
lowercase. If written with uppercase, we'll get the
following. And with the subsequent command, we'll
correct this.

»> c.Dir_travel_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Category' object has no attribute 'Dir_travel_set'
>» c.dir_travel_set
<django.db.models.fields,related.descriptors.create.reverse_many_to_one_manaqer .<locals>.RelatedManager object at 0x00000169F39C5270>

We obtained an object through which we can select all


Dir_travel posts. Let's select all posts
c.dir_travel_set.all()

»> c.dir_travel_set.all()
<QuerySet [<Dir_travet: Netherlands:*, <Dir_travet: Italy>, <Dir_travel: Germany:*, <Dir_travel: Poland:*, <Dir_travel: Ukraine>]>
Field filters.
< Attribute name >_ gte - Greater than or equal to
comparison (>=);
< Attribute name >_ Ite - Less than or equal to
comparison (<=);
Those are called lookups:_ gte and_ Ite, which allow
us to select records based on conditions of >= or < =
respectively.

For instance, let's move to the terminal and execute the


following command where the primary key is greater
than or equal to 2.
Dir_travel.objects. filter(pk_ gte=2)

»> Dir_travel,objects.filter(pk __gte=2)


<QuerySet [<Dir_travel: Tunisia?, <Dir_travel: Tanzania?, <Dir_travel: Netherlands?, <Dir_travel: Tanzania?, <Dir_travel: Zimbabwe?, <Dir_travel: Cambodia?, <Dir_travel: China?, <Dir_t
ravel: Canada?, <Dir_travel: United States?, <Dir_travel: Mexico?, <Dir_travel: Peru?, <Dir_travel: Brazil?, <Dir_travel: Italy?, <Dir_travel: Germany?, <Dir_travel: Poland?]?

Let's introduce a few more filters for familiarity. For


example, one that allows searching records by a text
fragment. In this case, let's say it's 'Ge'
Dir_travel.objects. filter(title_ contains='Ge')

»> Dir_travel.objects .filter(title_ contains=1 Ge’)


cQuerySet [<Dir_travel: Germany>]>

The second filter does the same thing but disregards


case sensitivity.
Dir_travel.objects. filter(title_ icontains='GE')
>>> Dir_travel.objects.fitter(title_ icontains=’ GE ’)
<QuerySet [<Dir_travet: Germany>]>
>» n

Keep in mind that in the SQLite database engine, this


filter doesn't work with Russian characters. However, in
other database systems, there are no such issues.
Another filter allows selecting records by the primary
key and its ID.
Dir_travel.objects. filter(pk_ in=[1,3,4])

»> Dir_travel.objects.filter(pk_ in=[l,3,4])


<QuerySet [<Dir_travel: Italy?, <Dir_travel: Germany?, <Dir_travel: Ukraine?]?

You can also use two conditions simultaneously


Dir_travel.objects. filter(pk_ in=[1,3,4],
is_published=True)

>» Dir_travel.objects.filterfpk_ in=[l,3,4], is_published=True)


<QuerySet [<Dir_travet: Italy>, <Dir_travel: Germany>, <Dir_travel: Ukraine>]>

These conditions should be true simultaneously. This


filter can also be used for a foreign key. For instance,
let's refer to the foreign key and retrieve all records
from the first and second categories.
Dir_travel.objects. filter(cat_ in=[1, 2])

>» Dir_travel.objects.filter(cat__in=[l, 2])


<QuerySet [<Dir_travel: Netherlands?, <Dir_travet: Cambodia?, <Dir_travel: China?, <Dir_tra
vel: Italy?, <Dir_travel: Germany?, <Dir_travel: Poland?, <Dir_travel: Ukraine?]?
n__________________________ _____________________________
Let's approach this a bit differently. First, let's read the
categories for which we'd like to select records.
cats = Category.objects.all()
And then...
Dir_travel.objects. filter(cat_ in=cats)

»> cats = Category.objects.aU()


??? Dir_travel.objects.filter(cat_ in=cats)
<QuerySet [<Dir_travel: Tunisia>, <Dir_travel: Tanzania?, <Dir_travel: Netherlands?, <Dir_t
ravel: Tanzania?, <Dir_travel: Zimbabwe?, <Dir_travel: Cambodia?, <Dir_travel: China?, <Dir
-travel: Canada?, <Dir_travel: United States?, <Dir_travel: Mexico?, <Dir_travel: Peru?, <0
ir_travel: Brazil?, <Dir_travel: Italy?, <Dir_travel: Germany?, <Dir_travel: Poland?, <Dir_
travel: Ukraine?]?
>» □ _______________________________________________

Let's see how to use the Q class to create conditions


not based on AND, but on OR and NOT.
Using this class allows for the application of the
following approaches:

1. & - Logical AND (priority 2)


2. | - Logical OR (priority 3)
3. ~ - Logical NOT (priority 1)
Let's go to the terminal and import the Q class. We'll
select all records where the primary key is less than 5
and the category equals 2.
Dir_travel.objects.filter(pk_ lt=5, cat_id=2)
The output will be...
<QuerySet []>
>» from django. db . models import Q
>» Dir_travet.objects.filterfpk_ lt=5, cat_id=2)
<QuerySet []>
>» n
We received an empty list because all records where
pk<5 belong to the first category. However, if we
combine these two conditions using OR, i.e., to select
records where pk<5 OR cat_id=2, then we'll obtain
the list of records.
Dir_travel.objects.filter(Q(pk__lt=5) | Q(cat_id=2))

>» Dir_travel.objects.filter(Q(pk_ lt=5) I Q(cat_id=2))


<QuerySet [<Dir_travel: Cambodia*, <Dir_travel: China*, <Dir_travel: Italy*, <Dir_travel: Germany*
, <Dir_travel: Poland*, <Dir_travel: Ukraine*]*

The very first example can be presented differently,


using the Q class
Dir_travel.objects.filter(Q(pk_lt=5) & Q(cat_id=2))

»> Dir_travet.objects.fiLter(Q(pk_ tt=5) & Q(cat_id=2))


<QuerySet []>

And let's apply the final operator that reverses the


condition.
Dir_travel.objects. filter(~Q(pk__lt=5) | Q(cat_id=2))

>>> Dir_travel.objects.fiLterC-Q(pk_ tt=5) | Q(cat_id=2J)


<QuerySet [<Dir_travel: Tunisia*, <Dir_travel: Tanzania*, <Dir_travel: Netherlands*, <Dir_travel:
Tanzania*, <Dir_travel: Zimbabwe*, <Dir_travel: Cambodia*, <Dir_travel: China*, <Dir_travel: Canad
a*, <Dir_travel: United States*, <Dir_travel: Mexico*, <Dir_travel: Peru*, <Dir_travel: Brazil*]*

In the Django ORM, there are several methods for


quickly retrieving records from a table.
To get the first record from a selection, you can use the
following method
Dir_travel.objects. first()
»> Dir_travet.objects.first()
<Dir_travet: Tunisia?
??? n

If you select all records, you'll see that this particular


record comes first.
Dir_travel.objects.all()

»> Dir.travel.objects.all()
<QuerySet [<Cir_tnavel: Tunisia?, <Cir_tnavel: Tanzania?, <Cir_tnavel: Netherlands?, <Bir_travel:
Tanzania?, <Dir_travel: Zimbabwe?, <Dir_travel: Cambodia?, <Cir_travel: China?, <Dir_travel: Canad
a?, <Dir_travel: United States?, <Cir_tnavel: Mexico?, <Dir_travel: Peru?, <Cir_travel: Brazil?, <
□ir_travel: Italy?, <Dir_travel: Germany?, <Dir_travel: Poland?, <Dir_travel: Ukraine?]?

We can change the sorting order by pk and then select


the first record.
Dir_travel.objects.order_by('pk').all()

??? Dlr_travel.objects.order_by(' pk’).all()


<QuerySet [<Dir_travel: Ukraine?, <Bir_travel: Poland?, <Bir_travel: Germany?, <Cir_travel: Italy?
, <Dlr_travel: Brazil?, <Dir_travel: Peru?, <Dir_travel: Mexico?, <Dir_travel: United States?, <Di
r_travel: Canada?, <Dir_travel: China?, <Dir_travel: Cambodia?, <Bir_travel: Zimbabwe?, <Dir_trave
1: Tanzania?, <Dir_travel: Netherlands?, <Dlr_travel: Tanzania?, <Dir_travel: Tunisia?]?

Dir_travel.objects.order_by('pk').first()

»> Dir_travel.objects.order_by( 'pk') .firstQ


<Dir_travet: Ukraine?
>» n

or
Dir_travel.objects.order_by('-pk'). first()
>» Dir_travel.objects .order_by(’-pk’).first()
<Dir_travel: Tunisia?

We can also take the last entry


Dir_travel.objects.order_by('pk').last()

»> Dir_travel.objects .order_by(’pk’).last()


<Dir_travel: Tunisia?
??? [

or
Dir_travel.objects.order_by('-pk').last()

??? Dir_travel.objects.order_by('-pk’).last()
<Dir_travel: Ukraine?
??? n

Furthermore, if our table contains fields named


time_create and time_update, we can apply the
following queries for selection:
For instance, if we need to retrieve the entry with the
latest date...
Dir_travel.objects.latest('time_update')

??? Dir_travel.objects.latest('time_update’)
<Dir_travel: Tunisia?
???

Or we'll select the entry that was most recently added


to the database
Dir_travel.objects.earliest('time_update')

»> Dir_travel.objects.earliest(' time_update’)


<Dir_travel: Ukraine?
>» [

Moreover, if we need to select the next or previous


entry relative to the current record, there are specific
methods available.
Let's say we're selecting a certain entry from the
table...
w = Dir_travel.objects.get(pk=6)

We want to retrieve the previous and next records


relative to this entry. To achieve this, we need to utilize
a specific method
get_previous_by_
w.get_previous_by_time_update()
So, first, we specify this method with an underscore at
the end, and then the name of the field based on which
we are seeking the previous entry.

>» w.get_previous_by_time_update ()
<Dir_travel: Brazil?
??? n

And for selecting the next entry


w.get_next_by_time_update()
>» w.get_next_by_time_updateO
<Dir_travel: Mexico?
>» n

Additionally, in these methods, conditions regarding the


next and previous entries can also be specified. For
instance, let's select the next entry where the primary
key (pk) is greater than or equal to 7
w.get_next_by_time_update(pk_ gt=4)

»> w.get_next_by_time_update(pk_ gt=4)


<Dir_travel: Mexico?
??? [

Let's consider the following two methods:

1. exists() - Checking the existence of a record.


2. count() - Getting the count of entries.

Don't forget! If you've exited the Django terminal, write


the command
Python manage.py shell
After that, we import all our models
from traveler.models import *
And after these two simple commands, we can continue
practicing. Let's add another category to the table.
Category.objects.create(name='Asia', slug='asia')

??? Category.objects.create(name^'Asia’, slug=’asia’)


^Category: Asia?
For this newly created category, there are no posts yet.
Let's test the exists() method for this category. This
method should return False if there are no records.
new = Category.objects.get(pk=6)
»> new = Category.objects.get(pk=6)
»> new
<Category: Asia?
»> n

To retrieve associated records from the Dir_travel


table, we use the method dir_travel_set. Remember to
start this method with a lowercase letter.

>>> new.dir_travel_set.existsf)
False
>» n

In this case, this method will return False. If you check


another category, you'll get the value True.
cl = Category.objects.get(pk=1)
And onward we go
c1.dir_travel_set.exists()

»> cl = Category.objects.get(pk=l)
>» cl
^Category :Europe >
>» cl.dir_travet_set. existsQ
True
>» n
To find out the number of entries for a specific
category, we use the second method by analogy.
c1.dir_travel_set.count()

And for the new category


new.dir_travel_set.count()

These methods can be applied to any selections. For


instance, let's choose some entries where the primary
key (pk) is greater than 3 and count the number of
these entries.
Dir_travel.objects. filter(pk__gt=3).count()

>>> Dir_travel.objects.fitter(pk_ gt=3).count()


13
>» n

Next, let's consider several aggregate functions.


In the simplest case, an aggregate function looks like
this:
Dir_travel.objects.count()
This one simply returns the count of posts. Other
aggregate commands are written within a special
method
aggregate()
For example, if we want to see what the minimum value
of a field is... cat_id
Dir_travel.objects. aggregate(Min('cat_id'))
In this case, we want to compute the minimum 'cat_id'
field.
To use any aggregate function, you need to import it
first.
from django.db.models import *

»> Dir_travel,objects.aggregate(Min(’cat_id’))
{’cat_id_ min': 1}
»> n

From the screenshot, we can see that the minimum


cat_id is 1. Alternatively, we can specify multiple
functions separated by commas
Dir_travel.objects.aggregate(Min('cat_id'),
Max('catjd'))

»> Dir_travel.objects.aggregate(Min(’cat_id’), Max(1cat_id’))


{’cat_id_ min’: 1, ’cat_id__max’: 5}
»> n

If we want to get different key names in the dictionary


instead of...
{'cat_id_ min': 1, 'catid_ max': 5}
Then you should write it as follows
Dir_travel.objects.aggregate(cat_min=Min('cat_id'),ca
t_max= Max('catjd'))
»> Dir_travel.objects.aggregate(cat_min=Min( 'cat_id'),cat_max= Max('cat_id'))
{'cat_min': 1, 'cat_max': 5}
»> n

You can also perform standard mathematical


operations.
Dir_travel.objects.aggregate(res=Sum('catJd') -
Count('catjd'))

»> Dir_travel.objects.aggregate(res=Sum(1cat_id') - Count('cat_id'))


{'res1: 27}
??? n

So, we sum the 'cat_id' values and then subtract the


count of 'cat_id' from that sum. Or we can calculate
the arithmetic mean of the 'cat_id' field
Dir_travel.objects.aggregate(res=Avg('cat_id'))
We can also apply an aggregate function not to all
records but by using a specific subset or selection.
Dir_travel.objects. filter(pk_ gt=4).aggregate(res=Avg
(cat_id))

»> Dir_travel.objects.fitterfpk__gt=4).aggregate(res=Avg('cat_id'))
{'res1: 3.25}

So, we select all records where the primary key is


greater than 4, and then we apply an aggregate
function to this subset.
In all our selections above, all fields were automatically
chosen, and only those fields we explicitly mentioned
were displayed in the console. For instance, if we
display fields with pk=1, we'll get...
w = Dir_travel.objects.get(pk=1)
w.title, w.slug

»> w = Dir_travel.objects.get(pk=l)
»> w
<Dir_travel: Ukraine?
»> w.title
'Ukraine'
>» w.slug
'Ukraine’
>>> n

But what if we don't want that, and we only need the


fields that we explicitly specify? There's a special
method for this, like..
Dir_travel.objects.values('title', 'cat_id').get(pk=1)

»> Dir_travel.objects.valuesf'title’, ’cat_id').get(pk=l)


{'title': 'Ukraine', ’cat_id’: 1}

This kind of query will execute faster than the previous


one. We can also utilize associated data.
Dir_travel.objects.values('title',
'cat_name').get(pk=1)
We select the 'title' field and choose the 'name' field
associated with the primary model 'Category' for the
first record with pk=1.
»> Dir_travel. objects. valuesf' title ’ , ’cat_ name get (pk=l)
{’title’: ’Ukraine’, ’cat_ name’: ’Europe ’}

So we retrieved associated data from both tables.


Alternatively, we can select associated data: 'title'
from the Dir_travel table and 'name' from the
Category table.
w = Dir_travel.objects.values('title', 'cat_ name')

>» w = Dir_travel.objects.values(’title 1, ’cat_ name’)


>» VI

cQuepySet [{’title1: ’Tunisia1, 'cat_ name': 'Africa'} t {'title': 'Tanzania', 'cat__ name’: 'Africa'}, {’tit
le' : 'Netherlands', 'cat_ name': 'Eurooe'} , {'title': 'Tanzania', 'cat__ name': 'Africa'}, {'title': 'Zinbab
we', 'cat__name': 'Africa'}, {'title': 'Cambodia', 'cat__ name’: 'Asia'} , {'title': 'China', 'cat__ name’:
'Asia'}, {'title': 'Canada', 'cat__name’: ' North America '} , {'title': 'United States', 'cat__name': ’
'NorthAmerica {'title': 'Mexico', 'cat_ name': 'SouthAmerica '}, {'title': 'Peru', 'cat__ name': '
{'title': 'Brazil', 'cat name’: ' South America '}, {'title': 'Italy', 'cat name': 'Eurooe'}, {'tit
le' : 'Germany', 'cat_ name’: 'Eurooe'}, {'title': 'Poland', 'cat__ name’: 'Eurooe'}, {'title': 'Ukraine', 'c
at__name ' : ’ Eurooe ' }]>

For clarity, we can output the list using a for loop.


for p in w:
print(p['title'], p['cat__name'])
»> for p in w:
__ print(p['title'], p['cat_ name1])

Tunisia Africa
Tanzania Africa
Netherlands Eurooe
Tanzania Africa
Zimbabwe Africa
Cambodia Asia
China Asia
Canada North America
United States North America
Mexico South America
Pe ru South America
Brazil South America
Italy Eurooe
Germany Eurooe
Poland Eurooe
Ukraine Eurooe

Here's how Django's ORM generally looks. Above, only


the principles of various methods within this ORM were
shown, and this material can only serve as a stepping
stone for a more ambitious study of this topic using the
official documentation.
Mixins - eliminating code duplication.
Let's extract all the code we have in the view classes
into a separate class called a Mixin. Mixins are
designed for consistent operation with objects.
For a better understanding of what mixins are, let's
imagine a situation with an online store that has
several different products, each sharing a set of
identical characteristics. These might include weight,
price, dimensions, identifier, and so on. For each
product, you could create a separate class listing all
these similar characteristics. However, for more concise
code and better comprehension, you can extract the
repetitive characteristics into a separate class, place it
separately, and thereby avoid cluttering the code.
In Python, thanks to the mechanism of multiple
inheritance, mixins can be added as a separate base
class with certain considerations. In our case, the
already created classes inherit some methods from the
base classes.
For instance, the class
Dir_travelHome inherits from ListView.
To utilize the functionality of mixins, this class needs to
be specified first. That is, if for some reason we have
repeating attributes in the first inherited class (mixin)
and in the second base class, the attributes from the
class positioned first will be applied.
class Dir_travelHome(DataMixin, ListView)
Let's eliminate code duplication in practice.

We'll open views.py and, upon reviewing the code,


we'll find that there is duplicated code
def get_context_data(self, *, objectJist=None,
**kwargs):
context = super().get_context_data(**kwargs)
context['menu'] = menu
context['title'] = 'Homepage'
context['cat_selected'] = 0
return context

class Dir_travelHome(ListView):
is ©r model = Dir_travel
19 ©r template_name = 'traveler/index.html'
20 ©t context_object_name = 'posts'

23 ©T - def get_context_data(self, *, object_list=None, **kwargs):


context = super().get_context_data(**kwargs)
context['menu'] = menu
context ['title ' ] = 'Main page'

context!'cat_selected'] = 0
return context

30 ©T H def get_queryset(self):
return Dir_travel.objects.filter (is_published=True)

Hdef about(request):
return renderfrequest, 'traveler/about.html', {'menu': menu, 'title': 'About sile'})

All of this can be optimized. First, let's define the class


DataMixin.
In Django, all additional utility classes are typically
specified in a separate file, which we'll name utils.
13 Project ▼ O Z -r —
v travels
> media
> M static
v El traveler
> El migrations
> M static
> M templates
> El templatetags
ft _Jnit_.py

admin.py
ftappspy
i^forms.py
models.py
r< tests.py
ft urls.py

util spy
views, py
v El travels

Next, in the views.py file, we'll cut out the fragment

menu = [{'title': " About the website ", 'url_name':


'about'},
{'title': " Add article ", 'url_name': 'add_page'},
{'title': " Feedback ", 'url_name': 'contact'},
{'title': " Sign in ", 'url_name': 'login'}
]

And move it to the created utils.py file.


Below, we'll define the class DataMixin
class DadtaMixin:
def get_user_context(self, **kwargs):
context = kwargs
cats = Category.objects.all()
context['menu'] = menu
context['cats'] = cats
if 'cat_selected' not in context:
context['cat_selected'] = 0
return context

The method get_user_context will create a default


template context.
We'll start with context = kwargs to form an initial
dictionary from the named parameters passed to the
get_user_context function. Next, we'll create a list of
categories: cats = Category.objects.all().
Previously, we were creating custom templates in the
file traveler_tags.py

And using the tag show_categories

@Oegister.inclusion_tag(' traveler/list_categories.html')
15 def show_categories(sort=None, cat_selected=0):
if not sort:
cats = Category.objects.attO
else:
cats = Category.objects.order_by(sort)
return {"cats": cats, "cat_selected": cat_selected}

In the base.html template, we were already displaying


our categories
{% show categories cat_selected=cat_selected %}

<li><a href="{% url 'home' %}">KoHTHHeHTbi</ax/li>


{% endif

44 {% show_categories cat_selected=cat_selected %}

<li class="share">
<p>Haui KaHan</p>
<a class="share-yt" href="#"x/a>
</li>
Let's do the following: Remove the line
{% show categories cat_selected=cat_selected %}
And instead, we'll specify the output of categories using
the category cats

{% for c in cats %}
{% if c.pk = = cat_selected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li> <a href="{{ c.get_absolute_url }}">
{{c.name}}</a></li>
{% endif %}
{% endfor %}

So, as it was initially for us. And this collection will be


passed to the template because we're forming such a
context.
context['cats'] = cats
□class DadtaMixin:
def get_user_context(self, **kwargs):
context = kwargs
cats = Category.objects.atl()
context['menu'] = menu
14 = context[1 cats'] = cats
if 'cat_selected’ not in context:
context[ 'cat_selected ' ] ~ 0
17 A return context

So that we can use the Category model, let's import it

class DataMixin:
def get_user_context(self, **kwargs):
context = kwargs
cats = Category.objects.all()
context['menu'] = menu
context['cats'] = cats
if 'cat_selected' not in context:
context['cat_selected'] = 0
return context

from .model import *


from .models import *

menu = [{'title': "About the site", 'url_name': 'about'},


{'title': "Addartide ", 1 url_name ' : 'add_page'},
{'title': "Feedback", 'url_name': 'contact'},
{'title': "Tocome", 'url_name': 'login'}
]

Oj. Hiclass DataMixin:


def get_user_context(self , **kwargs):
context = kwargs
cats = Category.objects.all()
context['menu1] = menu
context['cats'] = cats
15 * if 'cat_selected' not in context:
context['cat_selected'] = G
return context

Next, we set
context['cat_selected'] = 0
and check if 'cat_selected' not in
context. If we somehow define the **kwargs key within the
parameters of the function def get_user_context(self,
**kwargs),
then in the context if 'cat_selected' not in context, this key
will be present, and in this case, we won't override it. If this
key is absent, then by default, we create
context['cat_selected'] = 0.
Finally, we return the context using return context.

Now, moving to views.py, let's begin by importing


everything we've defined in utils:
from .utils import *
from . forms import *
from .models import *
10 from .utils import *

Next, in the class Dir_travelHome,


we'll start by specifying our mixin first. After that, we'll
eliminate duplication.

class Dir_travelHome(DataMixin, ListView):


model = Dir_travel
template_name = 'traveler/index.html'
context_object_name = 'posts'

def get_context_data(self, *, object_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='Homepage')
return dict(list(context.items()) +
list(c_def.items()))

Bclass Dir_travelHome(DataMixin, ListView):


14 Of model = Dir_travel
15 Of template_name = 'traveler/index.html'
16 Of context_object_name = 'posts'
17

def get_context_data(self, *, object_list=None, **kwargs):


context = superf).get_context_data(**kwargs)
c_def = self. get_user_context(title='Main Pa9e ')
return dict(list(context.items()) + list(c_def.items()))

We call the get_user_context method using self


because we can access all the methods of the base
class.
We pass it one named parameter,
title='Homepage'.
Everything else will be automatically added to the
context. The required common context will be
determined by both dictionaries - context and c_def.
Now we need to merge these two dictionaries by
creating a list from the first and second dictionaries:
dict(list(context.items()) + list(c_def.items())).
Let's see how all this will work. We'll run it as we
remember, using the terminal command

Python manage.py runserver

We see what the Dir_travelHome view class generates,


and everything works the same as before.
Let's repeat this operation for all the other view classes,
inheriting similarly from the previous DataMixin class.
PJclass AddPage(DataMixin, CreateView):
©T form_class = AddPostForm
©T template_name = 'traveler/addpage.html'

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self. get_user_context (title='Main page ’ )
return dict(list(context.items()) + list(c_def.itemsO))

And don't forget to change the title, where we'll write


'Add an article.' Then, we do the same thing.

class ShowPost(DataMixin, DetailView):


model = Dir_travel
template_name = 'traveler/post.html'
slug_url_kwarg = 'post_slug'
context_object_name = 'post'

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title= contextppost'])
return dict(list(context.items()) + list(c_def.items()))
□class ShowPost(DataMixin, DetailView):
73 O| model = Dir_travel
74 ©I template_name = 'traveler/post.html
slug_url_kwarg = 'post.slug'
76 context_object_name = 'post'

def get_context_data(self, *, oblect_list=None, **kwargs):


context = superO.get_context_data(**kwargs)
80 c_def = self.get_user_context(itle= context[’post'])
return dict(list(context.items()) + list(c_def.itemsO))

The post title (title = context['post']) is formed


based on the context
context = super().get_context_data(**kwargs),
which in turn was formed based on the method
().get_context_data of the base class DetailView.

Let's move on to the next class

class Dir_travelCategory(DataMixin, ListView):


model = Dir_travel
template_name = 'traveler/index.html'
context_object_name = 'posts'
allow_empty = False

def get_queryset(self):
return
Dir_travel.objects.filter(cat_slug=self.kwargs['cat_slug'],
is_published=True)

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='KameropuH -
1 + str(context['posts'][0].cat),
cat_selected =
context['posts'][0].catjd)
return dict(list(context.items()) +
list(c_def.items()))

6class Blr_travelCategory(BataMixin, Listview):


84 ®t model = Dir.travel
85 ©f template_nane = ’traveler/index.btml'
86 ©f context_cbject_name - 'posts'
87 ©f allow_enpty ~ False

def get_queryset(self):
return Bir_travel.objects.filter(cat__ slut - self. kwargs[ ’ cat_slug 1 ], is_put>lished=Tnue)

def get_context_data(self, *, oblect_llst=None, **kwargs):


context - superf).get_ccntext_data(**kwangs)
c_def = self .get_user_ccntext(title='categoty - 1 + str (context [’ posts '] [0]. cat),
cat_selecte< = context['posts 1][6].cat_ld)
return diet(list(context.items()) + list(c_def.items()))

Let's check if everything works as intended. Open and


refresh the page.
Ensure that everything works exactly as it did before
the current changes. When working with mixins, you
can use not only methods but also extract common
attributes.
In Django, alongside view classes, you can use
multiple mixins. Let's consider a mixin example related
to user authentication, called LoginRequiredMixin. It
allows restricting access to pages for unauthorized
users.
For more details, you can read the documentation here:
https://docs.djangoproject.com/en/4.1/topics/auth/defau
lt/
This mixin can be used in conjunction with the view
class. On our page, there is a tab 'Add an article’
Adding an article
Titte:

Article tex:

Photo:

Publication:

Category: | Category not selected~v~|

Add

We would like to allow only authorized users to add


articles and restrict access to this page for everyone
else.
To work with this mixin, it needs to be imported.
from django.contrib.auth.mixins import
LoginRequiredMixin

from django.contrib.auth.mixins import LoginRequiredMixin

Then we'll add LoginRequiredMixin to the AddPage


class, which handles the authentication page.

class AddPage(LoginRequiredMixin, DataMixin,


CreateView):
form_class = AddPostForm
template_name = 'traveler/addpage.html'
A ’
This mixin is already in effect. Let's verify by refreshing
the page.

Page not found <404>

Request Method: GET


Request URL: http: //127.0.0.1: SOO 0/ac cou nts/l og i n.;? n ext=fad d pa g e/

Using the URLconf defined in Travels.mis, Django tried these URL patterns, in this order:

The page is not found.


This could indicate that we are not authenticated. If we
go to the admin panel, authenticate, and then click on
the 'Add an article' tab, everything should work.
If we log out from the admin panel and then refresh the
page with the form, it will again show a 404 page.
That's what this mixin is responsible for.
But let's make our page more user-friendly. In other
words, if the user is not authenticated, they should be
redirected to the admin panel, where they can enter
their login and password or authenticate.

To do this, in the AddPage class, we'll include the


following attribute

class AddPage(LoginRequiredMixin, DataMixin, CreateView):


form_class = AddPostForm
template_name = 'traveler/addpage.html'
login_url = '/admin'

Rclass AddPage(LoglnRequlredMlxin, DataMixin, CreateView):


h Of form_ctass = AddPostForm
5 Of template_name = 1traveler/addpage.html'
©T togin_urt - ’/admin’
Which specifies the redirection address for an
unregistered user.
However, this isn’t the best practice to write it this way.
Therefore, let’s use the following function
login_url = reversejazy('home')
So that we can use names to construct routes and, in
this case, redirect to the main page.
But first, let's import this function
from django.urls import reverse_lazy

ffom django.http import Http404


from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView

Or, if we don't want to redirect to the main page but


explicitly indicate that an unauthorized user cannot add
an article, we can redirect them to page 303.
raise_exception = True

class AddPagefLoginRequiredMixin, DataMixin, CreateView):


36 ©t forni_ctass = AddPostForm
35 Of template_name = 'traveler/addpage.html1
36 ©f login_url = reverse_lazy('home')
37 ©f raise_exception = True

And then, if the user is not authorized, they will see the
page

O 127.0.0.1:8000/add page/

403 Forbidden
If the user logs in

And clicks on the 'Add Article' tab, they will see

Adding an article
Title:

Article text:

Photo:

Publication:

Category:

Add

This mixin only works in conjunction with view classes.


If we're using view functions, to restrict access, we
should use a decorator.
In this case
@login_required
We'll restrict unauthorized users from accessing the
'About' page

@login_required
def about(request):
return render(request, 'traveler/about.html', {'menu':
menu, 'title': 1 About page'})

@login_required
Hdef about(request):
32 A return render(request, 'traveler/about.html', {'menu': menu, 'title': 'Aboutttie site>

And, of course, this decorator needs to be imported.


from django.contrib.auth.decorators import
login_required

from django.urts import reverse_lazy


from django.contrib.auth.decorators import togin_required
from django.views.generic import ListView, DetaitView, CreateView

Now, if the user tries to access the 'About' page


without being logged in, they will encounter an issue

<- -> C (D 127.0.0.1:8000/accounts/logi n/? next=/about/

Page not found (404)

Request Method: GET


RequestURL: http: //127.0.0.1:800 0/ac cou nts/l og i n.'? next=/a bo uV
Without dwelling on this, let's ensure that unauthorized
users don’t see the 'Add Article' tab.
However, as soon as a user logs in, this tab
automatically appears, providing the ability to create a
post.
The easiest way to implement this functionality is
through DataMixin, as it helps us pass the main menu

menu = [{'title': "Aboutthe", 'url_name': 'about'},


{'title': "Add article ", 1 url_name ' : 'add_page'}
{ ' title ' : "Feedback " , ■ url_name ' : ' contact' },
{'title': 'Tocomein, 'url_name': 'login'}

Instead of the line


context['menu'] = menu
in the class DataMixin
we'll add the following few lines

class DataMixin:
def get_user_context(self, **kwargs):
context = kwargs
cats = Category.objects.all()

user_menu = menu.copy()
if not self.request.user.is_authenticated:
user_menu.pop(1)
context['menu'] = user_menu

context['cats'] = cats
if 'cat_selected' not in context:
context['cat_selected'] = 0

©1 HcLass DataMixin:
def get_user_context (self, **kwargs):
context = kwargs
cats = Category.objects.all()

user_menu = menu.copyO
if not self.request.user.is_authenticated :
16 user_menu.pop(1)
context['menu’] = user_menu

context[‘cats’] = cats
if 'cat_selected’ not in context:
context[’cat_selected'] = 0

When the get_user_context method is called, we


make a copy of the dictionary with user_menu =
menu.copy(), that is, the entire collection

menu = [{'title': " About page ", 'url_name': 'about'},


{'title': " Add Article ", 'url_name': 'add_page'},
{'title': " Feedback ", 'url_name': 'contact'},
{'title': " Log In ", 'url_name': 'login'}

We save the link to this menu in the variable


user_menu.
Then, if
if not self.request.user.is_authenticated:
i.e., if is_authenticated is False, the user is not
authenticated. If True, then they are authenticated
accordingly.
If the user is not authenticated, it means we remove
the second element from the user_menu collection
using user_menu.pop(1), which is at index 1.
{'title': " Add Article ", 'url_name': 'add_page'},
For unauthorized users, we won't have this line, but for
authorized ones, it will be present.
And in the context, we're already passing a link to this
menu
context['menu'] = user_menu
Let's go to our website and check.
If the user is not logged in, this menu item shouldn't be
present. If we access the admin panel and log in, this
menu item should appear.
And let's make one more improvement.
Let's go to the website and click on the 'Antarctica'
tab.

4- -> O Q 127.0.0.1:8000/category/antarcti

Page not found (404>

CniiGOK nycT. ho “Dir_travelCategory.allaw_empty”


Request Method: GET
Request URL: http: //127.0.0.1:SOO0/category/antaretica/
Ra i sed by: trave ler. views. Dir_trairel C ategory

As we can see, the page isn't found because there are


no posts in this category. Let's make it so that if any
category doesn't have any posts, it doesn't need to be
displayed.
Let's go to the DataMixin class and change the line
cats = Category.objects.all()
to
cats = Category.objects.annotate(Count(‘dir_travel'))
For the Count function to work, it needs to be imported
from django.db.models import Count

©1 class DataMixin:
def get_user_context(self, **kwargs):

r
context = kwargs
cats = Category.objects.all()
cats = Category.objects.annotate(Count('dir_travel'))

■from django.db.models irport Count HMMSI

Now, in the 'cats' collection, we have another property


related to the number of posts associated with this
category. We can use this attribute at the template
level.
Let's go to base.html and where the categories are
displayed, let's add a condition

{% for c in cats %}
{% if c.dir_travel_ count > 0 %}
{% if c.pk == cat_selected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li> <a href="{{ c.get_absolute_url }}">
{{c.name}}</a></li>
{% endif %}
{% endif %}
{% endfor %}
■{% for c in cats %}
{% if c.dir_travel_ count > 0 %}
{% if c.pk == cat_selected %}
<li class=" select ed">-({c. name}}</li>
{% else %}
<li> <a href="-{{ c. get_absolute_url }}">-{-{c. name}}</ax/li>
endif %}
■{% endif %}
{% endfor %}

If the object 'c', which is an element of the 'cats'


collection, has a value for the property
c.dir_travel__count > 0, indicating that this category
has at least one post,
then we display this post.
Now, we're only displaying categories that have at least
one post.
Let's update our website.

Continents

Asia

Africa

Europe

North America

South America

Our channel

a YouTube
And, as you can see, the category without any posts is
absent.

Pagination
Pagination, or as often called, paging, is needed when
there's a long list of posts on a page that impairs text
readability. This option allows breaking long lists into
multiple pages and displaying navigational links at the
bottom for each page, presented as easily accessible
links.
For more details on pagination, you can read at the
following link...
https://django.fun/ru/docs/django/4.1/topics/pagination/
It's worth noting that this option can be applied using
various approaches.
1. Can be used separately.
2. In ListView, where pagination is already
embedded.
3. Pagination handling in the template.
4. For view functions.
5. Using attributes and methods of the Paginator
class
Let's go over the first approach.
We'll open the Python console and, first of all, import
from django.core.paginator import Paginator
We'll use it with some list.
We'll take an arbitrary list, for example...
Dir_travel = ['Ukraine', 'poland', 'austria', 'germany',
'italy', 'spain', 'hungary']

9 I™ Python 3.10.7 (tags/v3.10.7:6cc6bl3, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on Win32
0. ►» from django.core.paginator import Paginator
+ Q Oir_travel = ['Ukraine1, 'poland', 'austria', 'germany', 'Italy', 'spain’, 'hungary']

And we'll work with this list.


First, we'll create an instance of the Paginator class
p = Paginator(Dir_travel, 3)
To do this, we pass the Dir_travel list to its constructor,
with which it will work.
3 - the number of items per page.
Now we can work with the properties of this class.
For example, let's determine the number of items in
this list.
p.count
p = Paginatep(Dir_travel, 3)
C* ►» p. count
+ 0 7

We'll obtain the number of pages


p.num_pages
We'll get 3, since 7/3 = 3 pages where elements will
be displayed.
If we want to work with the first page, the object 'p' has
such a method
pl = p.page(1)
page(1) - The page number we want to work with.
And now the link p1 will refer to the first page.
We'll get a list of this first page
p1.object_list

9 ’ pl - p.page(l)
C* ►» pl.object_list
+ © ['Ukraine', ’potand’, 'austria']

We can determine if there's a next page using the


method...
p1.has_next()

We can determine if there's a previous page using the


command...
p1.has_previous()
C* pl.has_previousO
4- © False

We can determine if there's any pagination at all using


the command...
p1.has_other_pages()

O, ►» pi .has_other_pages()
+ True

We'll determine the number of the next page


p1.next_page_number()

G, ►» pl.n ext_page_numberO
+ © 2

Now let's see how all of this can be used on our


website.
As we remember, the Dir_travelHome class is
responsible for displaying the main page, which is
inherited from the base class ListView.
So, pagination is already built into this base class.
We just need to add one line
paginate_by = 3
Where the number 3 represents the quantity of items
per page.
□class Dir_travelHome(DataMixin, ListView):
17 Of = paginate_by = 3
18 Of model = Dir_travel
19 Of templates a me - ’traveler/index.html’
context_object_name = 'posts’

If we load the web server and navigate to the main


page, refreshing it, we'll see only three posts.
However, if we use view functions instead of classes,
we'll need to write a bit more code.

def about(request):
contact_list = Dir_travel.objects.all()
paginator = Paginator(contact_list, 3)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'traveler/about.html',
{'page_obj': page_obj, 'menu': menu, 'title': About
the site'})

□def about(request):
contact_list = Dir_travel.objects.all()
paginator - Paginator(ccntact_list, 3)
page_number = request.GET.get('page')
38 ? page_obj - paginator.get_page(page_number)

return render(request, 1traveler/about.html1, {'page_obj': page_obj, 'menu': menu, 'title': '0 caiiTe'})

contact_list = Dir_travel.object.all()- We display a


list of countries
paginator = Paginator(contact_list, 3) - We create
a Paginator class
In order for us to use it, we need to import it
from django.core.paginator import Paginator
from django.contrib.auth.mixins import LoginRequiredMixin
8 from django.core.paginator import Paginator

page_number = request.Get.get('page') - We
retrieve the page number from the current GET
request, where we take the 'page' parameter formed
within this request.
page_obj = paginator.get_page(page_number) -
The list of elements on the current page, where we
access the instance of the paginator class and utilize a
function... get_page We access the page obtained
from the GET request, 'page_number,' and then the
'page_obj' list should be passed to the template
return render(request, 'traveler/about.html',
{'page_obj': page_obj, menu': menu, 'title': ‘About
the site’})

Next, let's open the about.html template and set up


the display of the list.
{% extends 'traveler/base.html' %}
{% block content %}
<h1>{{title}}</h1>
{% for contact in page_obj %}
<p>{{ contact }}</p>
{% endfor %}
{% endblock %}
extends 'traveler/base.html' %}

block content %}
<hl>{-{title}}</hl>

{% for contact in page_obj %}


<p>{{ contact }}</p>
{% endfor %}

10 {% endblock %}

We iterate through the page_obj collection and display


it as paragraphs <p>{{ contact }}</p>
Let's open the 'About the site' page and check how
everything is functioning.

Ab out the site

Tunisia

Tanzania

Netherlands

So, we've obtained a list of the first three countries. If


we want to move to the next page, we need to specify
the next page in the GET request using the 'page'
parameter

127.0.0.1:8000/about/?page=2
About the site
Tanzania

Zimbabwe

Cambodia

And so on.

Next, let's display a list of links to different pages. To do


this, let's go to the about.html template and add the
following lines of code.

<nav>
<ul>
{% for p in page_obj.paginator.page_range %}
<li>
<a href="?page={{ p }}">{{ p }}</a>
</li>
{% endfor %}
</ul>
</nav>
H<nav>
<ul>
{% for p in page_obj.paginater.page_range %}
<li>
<a href=,T?page={{ p }}">{-{ p }}</a>
</li>
{% endfor %}
</ul?
A</nav>

20 {% endblock %}

We access the page_obj object, which is a reference to


a specific page in the paginator, and then we access
the paginator property and its page_range method,
which returns an iterator allowing us to generate page
numbers. We return this iterator without parentheses
because in templates, all methods are called exactly in
this manner. The templating engine will call this
method itself. Next, using the 'for' tag, we'll generate,
at each iteration, the page number as a regular list
<li>
<a href="?page={{ p }}">{{ p }}</a>
</li>
Let's navigate to the page and see how everything
works.
About the site
Italy

Germany

Poland

• 1
• 2

• 4
• 5
• 6

Next, we won't improve anything further, as it was just


an example of pagination working with a view function.
Let's move on to view classes and do the same thing,
but this time on the home page. We've already done
something on the home page, namely, splitting it into
three posts. Let's add links to pages, just as we did in
the previous example. Let's open the base.html
template, which is responsible for displaying pages on
our site, and add the following lines

<nav class="list-pages">
<ul>
{% for p in paginator.page_range %}
<li class="page-num">
<a href="?page={{ p }}">{{ p }}</a>
</li>
{% endfor %}
</ul>
</nav>
<nav ctass='T’List-pages">

{% for p in paginator.page_range %}
<li class="page-num1T>
<a href="?page=-{-{ p }}’'>{{ p }}</a>
</li>
{% endfor %}
</ul>
</nav>

Followed the same pattern as the previous example.


Added a few styles
class="list-pages"
class="page-num"
To make them work, you need to add them to
styles.css

.list-pages {
text-align: center;
margin: 0 0 20px 0;
}
.list-pages ul {
margin: 20px 0 0 0;
padding: 0;
list-style: none;
}
.list-pages ul li {
display: inline-block;
margin: 0 20px 0 0;
}
.list-pages a {
color: #000;
font-size: 24px;
text-decoration: none;
}
.list-pages .page-num, .page-num-selected {
display: inline-block;
width: 60px;
height: 44px;
padding: 16px 0 0 0;
border: 1px solid #d0d0d0;
border-radius: 30px;
}
.list-pages .page-num:hover {
box-shadow: 3px 3px 1px #d0d0d0;
}
.list-pages .page-num-selected {
border: none;
color: #000;
font-size: 20px;
}
.list-pages .page-num-selected:hover {
box-shadow: none;

Next, using a 'for' loop, we iterate through the


page_range iterator. We access it directly through the
paginator object because in classes, in the base class
ListView, the paginator object is automatically passed
to the index.html template. Now let's see how all of this
will be displayed. Let's open our site.

Alright, sure, it's indeed better.


The index.html template is responsible for the main
page. It's quite logical to ask why we placed the code in
the base.html template.
It's all about practicality and following the 'Don't Repeat
Yourself' paradigm. The thing is, over time, there might
be a need for pagination in continent-specific
categories, if not already present. To avoid duplicating
code, it's more practical to write it once. If we navigate
through categories, for example, 'Europe,' we won't find
pagination there. We'll fix that later.

There's another aspect to consider. While using


pagination to select a page, we don't see which page
we're currently on. We need to somehow highlight this
number by disabling the link. This way, it stands out
among other buttons that are highlighted as links.

So, let's open base.html and make some modifications


to our code

<nav class="list-pages">
<ul>
{% for p in paginator.page_range %}
{% if page_obj.number == p %}
<li class="page-num page-num-selected">{{ p
}}</li>
{% else %}
<li class="page-num">
<a href="?page={{ p }}">{{ p }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>

<nav class="list-pages">
<ut>
{% for p in paginator.page_range %}
{% if page_obj.number == p %}
<li class="page-num page-num-selected">-{-[ p }}</li>
{% else %}
<li class="page-num">
<a href="?page=-{-{ p }}">-(-( p }}</a>
</li>
{% endif %}
endfor %}
</ul>
</nav?

If the current page number matches the number 'p'...


{% if page_obj.number == p %}
Then we display this list item as plain text
<li class="page-num page-num-selected">{{ p }}
</li>
Additionally, let's add styles for formatting
class="page-num page-num-selected"
It's all provided in the style.css file.
Otherwise {% else %}
The same list as before
<li class="page-num">
<a href="?page={{ p }}">{{ p }}</a>
</li>
Let's go to the main page and refresh.

12 3 4 5

That's exactly how it was intended.


Let's add our pagination to all pages of the site to avoid
duplicating code. For displaying categories, we rely
on...
Dir_travelCategory
If you take a closer look, in...
class Dir_travelCategory(DataMixin, ListView):
and
class Dir_travelHome(DataMixin, ListView):
there's a common class called DataMixin. That's where
we'll define the attribute...
paginate_by = 3
and consequently remove it from Dir_travelHome. As
a result, we'll have...
class DataMixin:
paginate_by = 3
Oj Rclass DataMixin:
paginate_by = 3
def get_user_context(self, **kwargs):
context = kwargs
cats = Category.objects.alt()
cats = Category.objects.annotate(Count('dir_travel'))

Let's navigate to the page and ensure everything is


functioning as intended. If we visit a page with fewer
than three posts, we'll see the number 1 without a link
because there are no more posts, indicating that in this
case, there's only one page

Let's ensure that when there are 3 or fewer posts—


meaning the entire list fits on one page—we don't need
to display any list. Let's navigate to the base.html
template and use a specific method.
has_other_pages
{% if page_obj.has_other_pages %}
66 <!-- Bnox KOHTeHTa
<div class="content-text">
{% block content %}
{W endblock %}
70 {% if page_obj.has_other_pages %}
Rcnav class=''list-pages">
<ul>
for p in paginator.page_range %}
if page_obj.number == p %}
<li class="page-num page-num-selected">{-{ p }}</li>
else %}
<li class="page-num">
<a href="?page=-{{ p }}">-{{ p }}</a>

endif %}
■{% endfor %}
</uL>
□ </nav>
{% endif %}
</div>

This method returns True if there are multiple pages


and False if there's only one page.
There's another aspect to consider. If we have a lot of
posts, there will be many corresponding links. It would
be optimal to display, for example, two numbers on the
left and two numbers on the right.
How can we do this? In our template, we have the
'page_obj' object with its 'number' property,
indicating the current page being displayed.
Additionally, in the template, we iterate over the 'p'
element using a 'for' loop
{% for p in paginator.page_range %}
We can set boundaries for this 'p' page using the 'add'
filter to display the list. For instance, we can subtract 2
page_obj.numberladd:-2
and increase by 2
page_obj.numberladd:2

Let's see how we can do this at the base.html


template level
<nav class='"list-pages'">
<ul>
{% for p in paginator.page_range %}
{% if page_obj.number == p %}
<li class='"page-num page-num-selected'">{{ p }}
</li>
{% elif p > = page_obj.numberladd:-2 and
page_obj.numberladd:2 %}
<li class='"page-num'">
<a href="?page={{ p }}">{{ p }}</a>
</li>
{% endif %}
{% endfor %}

</nav>
□tnav class="list-pages">
<ut>
{% for p in paginator.page_range %}
{% If page_obj.number == p %}
<U class="page-num page-num-selected">-(-{ p }}</li>
{% elif p >= page_obj.number|add:-2 and page_obj.number|add:2 %}
<li class="page-num">
<a href="?page=-{-[ p }}">-{{ p }}</a>
</li>
{% endif %}
{% endfor %}
f
83 A</nav>

If the object p is not the currently selected page and


falls within the selected range, i.e., greater than or
equal to -2 and less than or equal to +2
>= page_obj.numberladd:-2 and
page_obj.numberladd:2
Then we display the link
<li class="page-num">
<a href="?page={{ p }}">{{ p }}</a>
</li>

Let's add two more buttons that allow navigating to the


previous page and the next one.
{% if page_obj.has_previous %}
<li class="page-num">
<a href="?page={{
page_obj.previous_page_number }}">&lt;</a>
</li>
{% endif %}
{% if page_obj.has_previous %}
<11 class="page-num">
<a href="?page={{ page_obj.previous_page_number }}">&lt;</a>
</li>
{% endif %}

We're checking here if the previous page exists


page_obj.previous_page_number
using the method previous_page_number. If it returns
True, we can display this button as an angled bracket
'<' and form the link using the
previous_page_number.

In a similar manner, let's add a button for the next


page.
{% if page_obj.has_next %}
<li class="page-num">
<a href="?page={{
page_obj.next_page_number }}">&gt;</a>
</li>
{% endif %}

{% if page_obj.has_next %}
<li class="page-num">
<a href="?page={{ page_obj.next_page_number }}">&gt;</a>
</li>
{% endif %}

We're checking if there's a next page has_next


And create a link to the next page if it exists
next_page_number
as well as the right angled bracket
&gt;
Let's go to the website and refresh

User registration on the website


To access additional features, users can undergo a
registration process through a specialized form. For
instance, to have a personal account or leave
comments. We have the following fields available,
detailed information about which can be found on the
website in the admin panel.

Click on the 'Users' tab, then select the registered user


'root'. Currently, we only have one registered user.

On the next screenshot, we can see all the available


fields.
As seen in the screenshot, the following fields are
available to us: Username, Password, First Name, Last
Name, and Email Address. Additionally, there is a status
displayed below

Access rights

Q Active

Check if the user should be considered active. Uncheck this box instead of deleting the account.

E3 Staff status
Check if the user can enter the administrative part of the site.

□ Superuser status
Indicates that the user has al rights without being expUcily assigned.

So, we can request all these fields from the user during
registration. Let's start by adding a link for new user
registration.
First, let's navigate to the base.html template and
locate where the main menu is displayed. Then, we'll
slightly modify this block.

{% for m in menu %}
<li><a href="{o%o url m.url_name %}">
{{m.title}}</a></li>
{% endfor %}
<li class='last'><a href="{% url 'register'
%o}''>Pe^ucTpa^uH</a> | <a href="{%o url 'login'
%}''>BoMTu</a></li>

-{% for m in menu %}


<llxa href="-{% url. m.url_name %}">■{-[m.title}}</a></li>
-{% endfor %}
<li class="last"><a href="{% url 'register' %}"> Registration </a> | <a href="{% url 'login' %} ">to come </a ></!!>

Let's go to utils.py and remove the last item from the


main menu

menu = [{'title': " About the website ", 'urlname':


'about'},
{'title': " Add an article ", 'url name':
'add_page'},
{'title': " Feedback ", 'url_name': 'contact'}
]

Additionally, for the Registration button to work, we


need to define the 'register' URL route. Let's navigate
to urls.py and add the line
pathClogin/', login, name='login'),
path('register/', login, name='register'),
path('login/', login, na
path('register/’, login,

Let's go to the website and refresh the page.

Registration | To come in

Everything is functioning. However, the registration


isn't operational yet as it currently uses a simple
placeholder function. Let's fix that.
Navigate to urls.py and replace the line with:
path('register/', login, name='register'),
=>
path('register/', RegisterUser.as_view(),
name='register'),

path(’login/’, login, name=’login 1),


? path(’register/’, RegisterUser.as_view(), name=’register’),
path(1post/<slug:post_slug>/’, ShowPost.as_view(), name=1 post1),

Next, let's go to the views.py file and define the


RegisterUser view class. Since this class will work with a
form that will be inputting data into the database, we'll
inherit from the standard CreateView class.
Additionally, we'll include the standard DataMixin
mixin.

class RegisterUser(DataMixin, CreateView):


form_class = UserCreationForm
templatename = 'traveler/register.html'
success_url = reversejazy('login')

Hclass RegisterUser(DataMixin, CreateView):


form_class = UserCreationForm
117 ©T template_name = 'traveler/register.html'
118 ©T A success_url = reverse_lazy('login )

form_class - This attribute will reference the standard


Django UserCreationForm, which is used for user
registration.
template_name = 'traveler/register.html' - Link to
the template we'll be using.
success_url = reverse_lazy('login') - Redirect to the
URL upon successful user registration.

Next, let's prepare the context for the template. This


should be familiar to us already.

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title=' Registration
')
return dict(list(context.items()) +
list(c_def.items()))
□class RegisterUser(DataMixin, CreateView):
16 ©T form_class = UserCreationForm
17 ©T template_name = 'traveler/register.html'
18 ©T success_url = reverse_lazy('login')

def get_context_data(self, *, oblect_tist=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self. get_user_context(title='Registration ')

return dict(list(context.items()) + list(c_def.items()))

Next, we need to import the UserCreationForm class


so that we can use it.
from django.contrib.auth.forms import
UserCreationForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.forms import UserCreationForm
from django.core.paginator import Paginator

And let's create the register.html template. Add our


template to the app.
H Project ▼ Q £, -? 5 — views.py ifft register.html utils.py
v 3S
<!D0CTYPE html>
^styles, css
Hchtnfl tang="en">
> to images
H<head>
to js
<meta charset=ITllTF-8,l>
v to templates
to traveler 5 <title>Title</title>
about.html A</head>
[jJ addpage.html H<body>
rfJ base.html
rrJ index.html </body>
[?m list_categories.html
posthtml
ifj register.html
> to templatetags

Instead of the standard code, let's add the following


lines.

{% extends 'traveler/base.html' %}

{% block content %}
<h1>{{title}}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit"> Registration </button>
</form>
{% endblock %}
<% extends ’traveler/base.html’ %}

-{% block content %}


<hl>{{title}}</hl>

H<form method=,Tpost,T>
{% csrf_token %}
form.as_p }}
<button type=" submit11 Registration </but ton?
Ei</form>

L2 {% endblock %}

Expanding the base template


{% extends 'traveler/base.html' %}
Displaying a first-level heading
<h1>{{title}}</h1>
Below is all standard.
Let's see how this will work.

Let's go to our website and click on the 'Registration'


link.

Registration
Username: Required | | field. No more than 150 characters. Letters, numbers, and symbols only.

Password: | |

• The password should not be too similar to your other personal information.
* Your password must contain at least 8 characters.
* The password should not be too simple or common.
• The password cannot contain only numbers.

Password confirmation: ] To confirm, please enter your password again.


| Registration |
The standard form with three fields is displayed.
Let's improve the appearance of the form.
To do this, we will create our own form class in the
forms.py file

class RegisterUserForm(UserCreationForm):
class Meta:
model = User
fields = ['username', 'password^, 'password2']
widgets = {
'username': forms.TextInput(attrs={'class':
'form-input'}),
'password^: forms.PasswordInput(attrs=
{'class': 'form-input'}),
'password2': forms.PasswordInput(attrs=
{'class': 'form-input'}),
}

Rclass RegisterUserForm(UserCreationForm):
class Meta:
30 model = User
31 fields = ['username1, 'passwordl1, 'password2']
32 widgets = ■{
'username1: forms.TextInput(attrs=-{'class': 'form-input'}),
34 'passwordl': forms.PasswordinputCattrs={'class': 'form-input'}),
35 'password2': forms.PasswordinputCattrs={'class ' : 'form-input'}),
36 }

Let's import the base class UserCreationForm, which


we'll be extending
from django.contrib.auth.forms import
UserCreationForm
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User

Also, let's import the User model


from django.contrib.auth.models import User
We specify the fields that will be displayed in our form.
fields = ['username', 'passwords, 'password2']
and the formatting for each of these fields' widgets. The
names of these fields, namely 'username',
'passwordl', 'password2', can be found as follows:
Access the admin panel and then navigate to users

Change user

root

User name: root


Obligatory field. No more than 150 characters. Beech only

algorithm: pbkdf2_sha256 iterations: sol 390000


Password:

Passwords are stored in encrypted form, so there is no

Next, hover over the 'Username' field, right-click, and


select 'Inspect' to open the code inspector. Look for
the line
T<label class="required" for="id_username">
"Wmb no/ib3OBaTenfl:"
: :after*
</label>
<input type="text" name="username" value="root” class="vTextField" maxlength=
"150" autocapitalize-"none" autocomplete-="username" required id-"id_username">
== $e
► Cdiv class="help” id="id_username_helptext"> •- </div>
</div>
</div>

Similarly, locate the other fields. For instance, to identify the


password names, click on the form
hash: OK758m” ************************************

This user's password, but you can change it using this form.

And navigate

Change password: root

Enter a new password for the root user.

Password:

The password should not be too similar to your other personal information.

Your password must contain at least 8 characters.

The password should not be too simple and common.

The password cannot consist only of numbers.

The password again):

To confrm, please enter your password again.

And launch the code inspector for the 'Password' field


Cinput type="password" name="passwordl" autocomplete="new-password" autofocus
required id="id_passwordl"> == $0

And 'Password' (once again)

cinput type="password" name="password2" autocomplete="new-password” required id


"id_password2"> — $0
This way, using the browser, you can view the names of
all fields. In the view.py file, we should use this form.
Accordingly, in the RegisterUser class, we'll modify
the line...

class RegisterUser(DataMixin, CreateView):


form_class = RegisterUserForm
template_name = 'traveler/register.html'
success_url = reversejazy('login')

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title= Registration ')
return dict(list(context.items()) + list(c_def.items()))

Hclass RegisterUserfDataMixin, CreateView):


©I form_class = RegisterUserForm
©I temptate_name = 'traveier/register.htmt'
©t success_urt = reverse_Lazy(’Login')

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self .get_user_context(titte=’Registration')
return dict(List(context.items()) + List(c_def.items()))

Let's also add styles for all fields since the standard
Django method didn't apply styles to all fields.
Registration

Username: Obligatory field. No more

Password: | |

• The password should not be too similar to your other personal information
• Your password must contain at least 8 characters.
• The password should not be too simple and common.
• The password cannot consist only of numbers.

Password confirmation: | | Please enter to confirm

Registration

As we can see, styles were only applied to the


Username field. Therefore, we'll assign styles
somewhat differently.

class RegisterUserForm(UserCreationForm):
username = forms.CharField(label='Login',
widget=forms.TextInput(attrs={'class': 'form­
input'}))
email = forms.EmailField(label='Email',
widget=forms.EmailInput(attrs={'class': 'form­
input'}))
password1 = forms.CharField(label='Password',
widget=forms.PasswordInput(attrs={'class': 'form­
input'}))
password2 = forms.CharField(label='Confirm
password', widget=forms.PasswordInput(attrs=
{'class': 'form-input'}))

class Meta:
model = User
fields = ('username', 'email', 'passwordl',
'password2')

28 Hclass RegisterUserForm(UserCreationForm):
username = forms.CharField(label= 'Loain ', widget=forms.Textlnput(attrs={'class 1: 'form-input'}))
email - forms.EmailField(label='Email', widget-forms.EmailInput(attrs-{'class': 'form-input'}))
passwordl = forms.CharField(label='Password ', widget=forms.Passwordlnputfattrs-}'class': 'form-input'}))
password2 = forms. CharField (label='Password repeat ', widget=forms.Passwordinput(attrs=-['class': 'form-input'}))

34 H class Meta:
model = User
fields = ('username', 'email', 'passwordl', 'password2')

Next, let's open the register.html file and make some


adjustments.

Instead of the line


{{ form.as_p }}
Let's write it manually

{% extends 'traveler/base.html' %}

{% block content %}
<h1>{{title}}</h1>

<form method='"post'">
{% csrf_token %}

{% for f in form %}
<p><label class=''form-label" for="{{ f.id_for_label
}}">{{f.label}}: </label>{{ f }}</p>
<div class=''form-error">{{ f.errors }}</div>
{% endfor %}

<button type="submit"> Registration </button>


</form>

{% endblock %}

{% extends 'traveler/base.html' %}

■{% block content %}


<hl>{{title}}</hl>

Reform method="post">
{% csrf.token %}

{% for f in form %}
epxlabel class="form-label" for="{{ f.id_for_label }}">{{f.label}}: </label>{{ f }}</p>
<^lv class="form-error">{{ f.errors }}</div>
12 endfor %}

<button type="submit,,>Reqjstration </button>


A</form>

endblock %}

We're going through all the form fields


{% for f in form %}
And manually constructing all these fields
<p><label class="form-label" for="{{ f.id_for_label
}} ">{{f.label}}: </label>{{ f }}</p>
If there are any errors, they will be displayed using
<div class="form-error">{{ f.errors }}</div>
Let's go to the website, refresh the page, and
immediately enter the details for the new user
The entered password is too short. It must contain at least 8
characters. The entered password is too wide.
The entered password consists of numbers only.

| Registration |

As we can see, the system shows us errors if we've


done something incorrectly. If everything is done
correctly, it will redirect us to the next page
O 127.0.0.1:8000/login/

Authorization

That's because we specified it that way

□class RegisterUserfDataMixin, CreateView):


forni-Ctass = RegisterUsenFonm
117 Of template_name = 'traveler/register.html'
118 Of * success_url = reverse_lazy(’Login 1)

If you go to the admin panel and click on the Users


tab, you'll see the following window
Select user to edit

Q | || F"a I
Action: [--------- v|[ Run | Selected 0 items out of2

Q USERNAME A E-MAIL ADDRESS Name SURNAME STAFF STATUS

□ root [email protected] O

□ user_1 [email protected] O

If you click on the username, you'll see the following


window with settings
Change user

used

Username: user_1
Obi 93*07 fl«id htomowfhan 150 charAtXerv Oriyb

algorithm: pkdf2_sha256 iterations: 390000 sa


Password:

Passwords are stored encrypted. 90 no

Personal information
User authorization
First, in the views.py file, let's add a view responsible
for the authorization form.

class LoginUser(DataMixin, LoginView):


form_class = AuthenticationForm
templatename = 'traveler/login.html'

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='
Authorization ')
return dict(list(context.items()) +
list(c_def.items()))

Hclass Loginllser (DataMixin, LoginView):


form_class = AuthenticationForm
template_name = ’ traveler/login.html'

def get_context_data(self, *, oblect_List=None, **kwargs):


context = super().get_context_data(**kwargs)
133 c_def = self. get_user_context(title='Authorization ' )

return dict(list(context.items()) + list(c_def.items()))

This class will inherit from two base classes: DataMixin


and LoginView. LoginView contains all the logic for
the authorization of a new user. As we already know, to
make this class work, it is necessary to import.

from django.contrib.auth.views import LoginView


from django.contrib.auth.views import LoginView

AuthenticationForm is the standard authentication


form also provided by Django.
We also need to import it.
from django.contrib.auth.forms import
AuthenticationForm

from django.contrib.auth.forms import AuthenticationForm


from django.contrib.auth.views import LoginView

Our class, which we have just created, will use the logic
of the LoginView class and the form of the
AuthenticationForm class. The form will be displayed
in the template template_name =
'traveler/login.html'.
And for creating the context for this template

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='
Authorization ')
return dict(list(context.items()) +
list(c_def.items()))

Let's create the login.html template


v M traveler {% extends ' traveler/base.html1 %}
v M css
styles.css
block content %}
> images
<hl>{-{title}}</hl>

M templates
v M traveler Reform method="post">
ifl abouthtml {% csrf_token %}
iTm addpage.html {-{ form.as_p }}
base.html
index.html ebutton type=" submit"xo come i</button>
list_categories.html Be/form>
login,html
post.html
13 endbtock %}
im register.html

It's a standard one. Let's create it similarly to the


template for registration. Next, let's connect the view
class to the route. Go to urls.py and add the line.

pathClogin/', loginUser.as_view(), name='login'),

path('login/', LoginUser.as_view(), name='login'),


path('register/', RegisterUser.as_view(), name='register'),

And finally, comment out or delete the function that


was responsible for the placeholder page.

Hdef login(request):
return HttpResponse ("Authorization ")

We go to the website, click 'Login', and the standard


authorization form is displayed.
Authorization
Username: I-

Password: | |

And this form is already functional.


If you enter the data of a previously registered user into
the fields, the corresponding page will be displayed

Page not found (404)

Request Method: GET


Request U RL: http://127.0.0.1:80OO/acecunts/protile/

Since we don't have a handler for that address, we see


the 404 page.
Let's redirect to the main page of the site. Open
views.py, find LoginUser, and add the method
def get_success_url(self):
return reversejazy('home')

Hclass Loginllser(DataMixin, LoginView):


127 ©1 form_class = AuthenticationForm
128 ©T template_name = ' traveler/login. html’

def get_context_data(self , *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self .get_user_context(title='Authorization ')
return dict(list(context.items()) + list(c_def.itemsO))

135 ©T R def get_success_url(self):


136 A V return reverse_lazy('home')
It will be called if the user entered the login and
password correctly, i.e., the form passed validation, and
we redirect to the main page. Let's see how it will work.
Open the authorization form and enter the user's data.

Authorization
Username: |user 2 |

Password: ....... |

And we are on the main page. If you go to


http://127.0.0.1:8000/admin/,
you will see

You can also do the same thing a bit differently, in the


configuration package. Let's check how it works and
then revert it back. Comment out the last lines

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

Open the settings.py file and add the line


_inn_.py
ft.asgi.py
ft. settings.py
ft. urls.py
ft. wsgi.py
ft db.sqlite3
ft. manage.py

LOGIN_REDIRECT_URL = '/'

DEFAULT_AUTO_FIELD = 1django.db.models.BigAutoField1

MEDIA-ROOT = os.path.join(BASE_DIR, 'media')


MEDIA-URL = '/media/'

133 LOGIN_REDIRECT_URL =

If you log in with the credentials of a registered user


again, everything will work exactly as in the previous
example. That is, you can do it one way or another.
Let's improve the appearance of the authorization form.
Let's create another authentication form class and
name it
LoginUserForm

In the forms.py file, let's write the class


class LoginUserForm(AuthenticationForm):
username = forms.CharField(label=' Login ',
widget=forms.TextInput(attrs={'class': 'form­
input'}))
password = forms.CharField(label=' Password ',
widget=forms.PasswordInput(attrs={'class': 'form-
input'}))

39 Hclass LoginUserForm(AuthenticationForm):
username = forms.CharField (label='JlorMH', widget=forms.Textinput(attrs={'class': 'form-input'}))
41 Of B password = forms. CharField(label=' Flapo/ib', widget=forms. Passwordinput (attrs={ ’ class ' : 'form-input'}))

We will extend the base class AuthenticationForm. It


needs to be imported
from django.contrib.auth.forms import
AuthenticationForm

The class 'class Meta:' as in the form above does not


need to be created.
You can add more fields, for example, the email field.
But we won't add it now. Next, go to views.py and in
the LoginUser class, insert our LoginUserForm class
instead of AuthenticationForm. We imported this
class at the very beginning. At this stage, we can
remove that line.

class LoginUser(DataMixin, LoginView):


form_class = LoginUserForm
template_name = 'traveler/login.html'

□class LoginUserfDataMixin, LoginView):


form_class = LoginUserForm
template_name - ’traveter/login.html’

Let's not stop here and continue improving the form.


Open login.html and instead of
{{ form.as_p }},
write the following lines
{% for f in form %}
<p><label class="form-label" for="{{ f.id_for_label
}}">{{f.label}}: </label>{{ f }}</p>
<div class="form-error">{{ f.errors }}</div>
{% endfor %}

{% for f in form %}
<pxlabel class="form-label" for="{{ f.id_for_label }}">{{f.label}}: </label>{-{ f }}</p>
<div class="form-error">{-{ f.errors }}</div>
{% endfor %}

Additionally, at the top, we will display general errors


during user authentication (incorrect login and
password)
<div class="form-error">{{ form.non_field_errors }}
</div>

csrf_token
<div ctass="form-error">-{-{ form. non_fietd_errors }}</div>

Let's go to the website and check the functionality of


this form. Enter incorrect login and password

Authorization
Please enter the correct username and password. Both fields can be case sensitive

Login:

Password:

Toccmam

This line, highlighted in red, appeared thanks to the


code
<div class="form-error">{{ form.non field errors }}
</div>
Next, if the user is authenticated, there is no need to
display the Registration and Login links; instead,
display the Logout link and a welcome message.

Registration | To come in

Let's implement this. Go to the base template,


base.html, and find the section where the main menu
is displayed.

{% block mainmenu %}

<div ctass=ri header"?


cut id="niainmenu" ctass="m
<ti ctass=,1togo"?ca href="
for m in menu %}
<Li><a href="{% urt m.url
{% endfor %}
<Li ctass=,,tast"?<a href=
</ut?
cdiv ctass=,,clear"?</div?
</div?
endbtock mainmenu %}

And before the line


<li class="last"><a href="{% url 'register' %}">
Registration </a> | <a href="{% url 'login' %}"> Log
In </a></li>
Let's write an additional condition. If the current user is
authenticated...
{% if request.user.is_authenticated %}
...then
<li class="last"> {{user.name}} | <a href="{% url
'logout' %}"> Logout </a></li>
...we will display their name {{user.username}}
...and a link <a href="{% url 'logout' %}"> Logout
</a>
Otherwise {% else %}
<li class="last"><a href="{% url 'register' %}">
Registration </a> | <a href="{% url 'login' %}"> Log
In </a></li>

{% if request.user.is_authenticated %}
25 = <11 class='Tlast"> {{user.username}} I <a href="{% url 'logout' %}">Toconneia/a></li>
26 {% else %}
<11 class="last"><a href='T{% url 'register' %}'r>Reaistration </a> I <a href=,T{% url 'login' %}'r>Tocomeifi/a></li>
{% endif %}

Let's add the 'logout' route. Go to urls.py and add the


corresponding line of code
pathClogout/', logout_user, name='logout'),

path('login/', LoginUser.as_view(), name='login'),


path('logout/1, logout.user, name='logout,
path('register/', RegisterUser.as_view(), name='register,

Define the logout_user function in views.py

def logout_user(request):
logout(request)
return redirect(‘login')

def logout_user(request):
logout(request)
return redirect('login 1)
There is no need to write a whole class, as the function
itself is very simple. This function calls the standard
logout function, which needs to be imported.
from django.contrib.auth import logout

f®)m django.contrib.auth.decorators import togin_required


6 from django.contrib.auth import logout
from django.views.generic import ListView, DetaitView, CreateView

Go to the website, log in with your username user_4


user_4 | BbiwTW

If you click Logout, you will be redirected to the


http://127.0.0.1:8000/login/
page and see on the right

Registration | To come in

and
127.0.0.1 8000/login/

Authorization

Password:

Tooarw ri

Let's make another improvement. It would be logical for


the user to be automatically authenticated upon
registration.
Go to views.py, find the RegisterUser class, and add
the following at the end
class RegisterUser(DataMixin, CreateView):
form_class = RegisterUserForm
template_name = 'traveler/register.html'
success_url = reversejazy('login')

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title= Registration ')
return dict(list(context.items()) + list(c_def.items()))

def form_valid(self, form):


user = form.save()
login(self. request, user)
return redirect('home')

124 ©r def form_valid(self, form):


125 user = form.save()
126 login(self.request, user)
127 return redirect('home 1)

The form_valid method is called upon successful


validation of the new user registration form.
We save the form to the database
user = form.save()
By calling login(self.request, user), we authenticate
the user and redirect them to the homepage
return redirect('home')
And, of course, don't forget to import the function login
from django.contrib.auth import login

fOam django.contrib.auth import logout


from django.contrib.auth import login

To test, register a new user, and if everything goes


smoothly, they will be already authenticated.
Registration

Everything is as intended

user_6 I Go out

You can read more details about authentication at the


following link
https://docs.djangoproject.eom/en/4.1/topics/auth/defau

Caching pages
SQL queries are executed to generate pages for our
website. It's important to understand that each client
request involves the creation of pages and
corresponding SQL queries.
Let's say your website is visited by 100-200, or even
1000-2000 people per day, and there's potential for
even more. For a typical website, the norm might be
several hundred thousand, or even a million queries per
day. How can you reduce the load on your website and
database?
To address this, caching mechanisms for pages were
introduced. You can find more detailed information at
the following link:
https://docs.djangoproject.com/en/4.1/topics/cache/
The idea behind caching is that if page updates are
infrequent and there are numerous requests for the
same page, it makes sense to generate the page the
first time and serve the previously generated HTML
document on subsequent requests.
This significantly reduces the load on databases and
servers.
Memory caching
Caching in Django can be implemented either at the
memory level using Memcached, at the database level,
or at the file system caching level - the most common
caching method. Let's consider this method for
organizing file system caching.
Navigate to settings.py and define a dictionary. Find the
standard lines of code in the documentation, copy
them, and paste them in

CACHES = {
'default': {
'BACKEND':
'django.core.cache.backends.filebased.FileBasedCach
e',
'LOCATION': 'c:/foo/bar',
}
}

Next, we need to specify the path to the root folder of


the cache itself.
Replace
'LOCATION': 'c:/foo/bar' with 'LOCATION':
os.path.join(BASE_DIR, 'travels_cache').
'travels_cache' is an arbitrary name for the folder that
needs to be created.
BASE_DIR is a constant that holds the root directory of
the project.
CACHES = ■(
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache1,
'LOCATION': os.path.join(BASE_DIR, 'travels_cache')
138 ?

139 }_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

travels sources root, hon\Dj


M travels
> k media
> static
> El traveler
v El travels
ft _init_.py
ftasgipy

settings.py
ft urls.py
&wsgi.py

travels_cache
db.sqliteS
manage.py

If you read the caching documentation, various


parameters are presented - you can cache the entire
site.
However, we will cache individual types of pages at the
class (or view) level.
This means we can cache individual classes (views).
We'll be working with classes.
Open the urls.py file. Let's start with caching the main
page.
Import the decorator
from django.views.decorators.cache import
cache_page
Based on this decorator, both functions and classes can
be cached.
Let's cache the main page
path(", cache_page(60)(Dir_travelHome.as_view()),
name='home'),

cache_page(60) - The cache will be stored for 60


seconds.
(Dir_travelHome.as_view()), - The view class that we
are caching.
Let's check how this will work.
After loading the page, go to the cache folder created
earlier in our project, and you'll see two files that store
the cached page

travel s_cache
jf?92123fc23b9e1a80ba6452d6dd302b95.djca
if?f6381ebc4d99f5b31350d62738758249.djca(

If we refresh the page, as we expect, there should be


no SQL query,
and the page generation time will be significantly
reduced. This query will work again exactly after 1
minute.
If the content of the page changes during this time, the
user won't notice any difference upon refresh because
their page will be generated from the cache.
If the page is dynamically changing, for example, a
page with comments, caching such a page is not
advisable.

Caching at the request level


Let's go to urls.py and bring back the line...
pathC', cache_page(60)(Dir_travelHome.as_view()),
name='home'),
to its initial state
pathC', Dir_travelHome.as_view(), name='home'),

Next, let's consider another method - template-level


caching, where we can cache individual fragments.
For example, let's cache the sidebar, as it requires a
single SQL query for its formation. Right before the
sidebar, load the tag...
{% load cache %}
Next...
{% cache 60 sidebar %} - We write the tag, pass the
time 60 seconds, and the name of the cache itself
(key) - 'sidebar', under which the cache will be stored.

{% load cache %}
<td valign="top" class="left-chapters">

<ul id="leftchapters">
{% cache 60 sidebar %}
{% if cat_selected == 0 %}
<li class="selected">continents </li>
{% else %}
<lixa href="-{% url 'home' %}">continents</ax/li>
{% endif %}

{% for c in cats %}
{% if c.dir_travel_ count > 0 %}
{% if c.pk == cat_selected %}
<li class=" selected">-{-{c . name}}</li>
{% else %}
<li> <a href="-({ c.get_absolute_url }}">{-{c.na[ne}}</ax/li>
{% endif %}
{% endif %}
{% endfor %}
57 {% endcache %}

At the point where the sidebar formation ends, write


the tag...
{% endcache %}
That's it.
Everything should work as intended. Let's consider one
more caching method.
Low-level API caching
Let's remove the recent changes, as in the previous
example. There are the following functions
cache.set() - saving arbitrary data to the cache by
key.
cache.get() - retrieving arbitrary data from the cache
by key.
cache.add() - sets a new value in the cache if it
doesn't exist there yet.
cache.get_or_set() - fetches data from the cache, and
if it doesn't exist, automatically adds the default value.
cache.delete() - deleting data from the cache by key.
cache.clear() - complete cache clearance.

Let's go to utils.py, find the DataMixin class, and


cache the SQL query
cats = Category.objects.annotate(Count('dir_travel'))
Import the module
from django.core.cache import cache

from django.db.models import Count


from django.core.cache import cache

Next
cats = cache.get('cats')
if not cats:
cats =
Category.objects.annotate(Count('dir_travel'))
cache.set('cats', cats, 60)
cats = cache.get('cats')
if not cats:
cats = Category.objects.annotate(Count(’dir_travet'))
cache.set('cats ', cats, 60)

Read the 'cats' collection using the get('cats') function


with the key 'cats'. Perform a check.
If the value of 'cats' is 'None,' indicating that the data
has not been read, then we will read this data from the
table
cats = Category.objects.annotate(Count('dir_travel'))
We'll cache the data
cache.set('cats', cats, 60)

It's important to remember that this caching tool should


be enabled at the final stages of website development.
Caching can hide many SQL queries and thereby
mislead the developer.
m.
Feedback form
our website looks quite finished. However, if you click
on the link...

Feedback

we will end up on a placeholder page

<- -> C 0 127.0.0.1:8000/contact/

Feedback

Let's fix that.


Go to the views.py file, and instead of the function...
def contact(request):
return HttpResponseC'Feedback ")
let's define a view class to generate this page.
class ContactFormView(DataMixin, FormView):
form_class = ContactForm
templatename = 'traveler/contact.html'
success_url = reversejazy('login')

def get_context_data(self, *, oblect_list=None,


**kwargs):
context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='Feedback')
return dict(list(context.items()) +
list(c_def.items()))
def form_valid(self, form):
print(form.cleaned_data)
return redirect('home')

□class ContactFormViewfDataMixin, FormView):


form_class = ContactForm
template_name = 'traveler/contact.html'
success_url = reverse_lazy('login')

def get_context_data(self, *, oblect_list=None, **kwargs):


context = super().get_context_data(**kwargs)
c_def = self. get_user_context (title='Feedback1)
return dict(list(context.items()) + list(c_def.items()))

79 □ def form_valid(self, form):


print(form.cleaned_data)
return redirect('home')

This class will inherit from the standard classes


DataMixin and FormView.
FormView is a standard base class for forms that are
not tied to a model, hence it won't interact with the
database.
If the form is successful, it will redirect to the home
page. 'def get_context_data' forms the context for
the template, and we're already familiar with it.
'def form_valid' is called when the user correctly fills
in all the fields of the contact form.

Let's add ContactForm in the forms.py file.

class ContactForm(forms.Form):
name = forms.CharField(label='WMH',
max_length=255)
email = forms.EmailField(label='Email')
content =
forms.CharField(widget=forms.Textarea(attrs=
{'cols': 60, 'rows': 10}))

Hclass ContactForm(forms.Form):
name = forms.CharField(tabel='Name', max_length=255)
email = forms.EmailField(label='Email1)
content = forms.CharField(widget=forms.Textarea(attrs={'cols': 60, 'rows': 10}))

This class will inherit from the general Form class and
will contain three fields.
Next, let's associate the ContactFormView with the
route

path('contact/', ContactFormView.as_view(),
name=,contact'),

path('addpage/', AddPage.as_view(), name='add_page1),


10 ? path('contact/', ContactFormView.as_view(), name='contact1),
path('login/', Loginllser .as_view() , name= 'login 1),

Next, let's add the template.


M templates
v M traveler
[fS abouthtml
ifj addpage.html

base.html
contact.html
index.html
। list_categories.fr
login.html
post.html
itJ register.html
{% extends 'traveler/base.html' %}

{% block content %}
<h1>{{title}}</h1>

<form method="post">
{% csrf_token %}
<div class="form-error">{{ form.non_field_errors }}
</div>
{% for f in form %}
<p><label class="form-label" for="{{ f.id_for_label
}}">{{f.label}}: </label>{{ f }}</p>
<div class="form-error">{{ f.errors }}</div>
{% endfor %}

<button type="submit"> Send </button>


</form>

{% endblock %}
{% extends 'traveler/base.html1 %}

{% block content %}
<hl>{{title}}</hl>

H<form method="post">
{% csrf.token %}
<div class="form-error">{{ form.non_field_errors }}</div>
{% for f in form %}
<pxlabel class="form-label" for="{{ f.id_for_label }}">{{f.label}}: </label>{{ f }}</p>
<div class="form-error">-{-{ f.errors }}</div>
{% endfor %}

cbutton type="submit">Send </button>


</form>

17 {% endblock %}

We won't repeat the process as we've added it multiple


times.
Let's go to our website, click on 'Feedback,' and see...

If you fill out the form with any data and submit it, you
will be redirected to the home page, as intended
return redirect('home')

Let's go to the terminal and find the message that


displays all the data we submitted in the form

[10/Mar/2023 12:39:15] "0ET /contact/ HTTP/1.1" 200 3024


{‘name’: 'MAX 'email': ’[email protected]’, 'content': 'Hello!'}
[10/Mar/2023 12:42:43] "POST /contact/ HTTP/1.1" 302 0

Fine-tuning the admin panel for the


developed website
More details can be found on the website
https://docs.djangoproject.eom/en/4.1/ref/contrib/admin
/.
Let's see how to customize the styling of the admin
panel.
admin/base_site.html is the base template for
creating admin panel pages. Let's take a look at its
content.
Navigate to the venv directory, then lib, followed by the
site-packages directory. In this directory, find django,
then contrib, then admin. Inside the admin directory,
locate the templates folder, go into the admin folder,
and there you'll find the file base_site.html.

tovenv
to Include
v to Lib
v to site-packages
> El _distutils_hack
> El asgiref
> to asgiref-3.5.Z.dist-info
> El captcha
> to captcha-0.4. di st-info
v El django
> to apps
> El conf
v to contrib
v El admin
> to locale
> El migrations
> to static
v totemplates
to admin
to admin
> to auth
> to editjnline
> to includes
> to widgets
404. h tn I
a 5D0.html

actions.html
app_index.html
app_list.html
base.html
base_site.html
ch a n g e _fo rm. htr

And its contents

{% extends "admin/base.html" %}

{% block title %}-{% if subtitle %}{{ subtitle }} I -[% endif %}{{ title }} | {-[ site_title | default1 Django site admin'

{% block branding
<hl id="site-name"><a href="{% url 'admin:index' %}">-{-{ site_header|defaultDjango administration') }}</a></hl>
{% endblock %}

{% block nav-global %}{% endblock %}

We can make changes directly in this file, but it is not


the best practice. It's better to override it directly in our
project.
To override this file, let's create a folder at the root of
our project called 'templates.' Inside this directory,
create a folder named 'admin’.
If you place a file named base_site.html in this
directory, it will override the file with the exact same
name that we looked at earlier.

travels es root, C:\Python\Django\travels {% extends "admin/base.html" %}


v to travels
> to media {% block title %}{% if subtitle su
> to static
v to templates
{% block branding %}
v to admin
<hl id="site-name"xa href="{% url 'adm
ij base_site.html
> to traveler
{% endblock %}
> to travels
> to travels_cache {% block nav-global %}{% endblock %}

And copy all the contents from the previous file into this
one.

{% extends "admin/base.html" %}

{% block title %}{% if subtitle %}{{ subtitle }} | {%


endif %}{{ title }} | {{ site_titleldefault:_('Django
site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index'
%}">{{ site_headerldefault:_('Django
administration') }}</a></h1>
{% endblock %}

{% block nav-global %}{% endblock %}

Since the new path to this file is non-standard, we need


to explicitly specify it in the settings.py file under the
TEMPLATES section.
'DIRS': [os.path.join(BASE_DIR, 'templates')],

□TEMPLATES = [

'BACKEND 1: ' django.template.backends.django.DjangoTemptates',


59 ? 'DIRS': [os.path.join(BASE_DIR, 'templates')],
60 'APP_DIRS': True,
61 'OPTIONS': {

Reload the server.


And, as we can see, nothing has changed.
We see the same admin panel, but now it's our own,
not the built-in one. And now we can override it without
affecting the existing built-in panel.

What do we see in this template?!


{% block title %} - block for the title.
{% block branding %} - Django administration

Django administration

{% block nav-global %} - Navigation block.


Let's add our own styling. If you open the higher-level
template base.html, you'll see the block:
{% block extrastyle %}{% endblock %}
If we write styles in this block, they will be applied in
the <head> section accordingly. Let's place it
somewhere near the beginning.
The content of this block will link to our style sheet,
which will add new styles to the admin panel.

{% extends "admin/base.html" %}
{% load static %}
{% block extrastyle %}
<link rel="stylesheet" href="{% static
'css/admin.css' %}">
{% endblock %}

{% block title %}{% if subtitle %}{{ subtitle }} | {% endif


%}{{ title }} | {{ site_titleldefault:_('Django site admin') }}
{% endblock %}

{% block branding %}
<h1 id='"site-name'"><a href='"{% url 'admin:index' %}'">
{{ site_header|default:_CDjango administration') }}</a>
</h1>
{% endblock %}

{% block nav-global %}{% endblock %}

Include it
{% load static %}

1 {% extends "admin/base.html" %} ReaderMode


{% toad static %}
{% block extrastyle %} □ <, •»
■clink rel="stylesheet" href="-{% static 'css/admin.css' %}">
{% endbtock %}

{% block title %H% if subtitle %}{{ subtitle }} I {% endif %H{ title }} I {{ site_titte I def ault:_(‘ Django site admin') }}{% endblock %}

block branding %}
<hl id="site-name"xa href="{% url 'admin:index' %}''>{{ site_header|defaultDjango administration’) }}</ax/hl>
LI {% endbtock %}

13 {% block nav-global endbtock


Next, we should specify 'css/admin.css'

V k travels sources root C:\Python\Django\travels


v M travels
> M media
> M static
> k templates
v El traveler
> El migrations
v M static
v css
xti admin.css
v B traveler
> M css
> M images

If you go to the admin page, refresh, and use the file
inspector, you will see that our file is successfully linked

dink rel="stylesheet" hr e-F="/static/admin/css/nav sidebar.css")


< sc nipt src=" /static/admin/is /nav s idebar. j s" defenx/script>
dink rel=rr stylesheet" hne-F="/static/ css/admin ■ css" >

How to find out which selectors are used, for example,


to change the color of the header?

Django administration

Using the browser, let's inspect the element code


Copy the id = 'header'.
With this identifier, we can now work with the header.
Go to our admin.css file and add the selector. In it,
change the background color to any arbitrary color.
#header {
background: #088A08;
}

Sheader ■{
background: S088A08;
3 }

Go to the admin panel page and refresh

Let's proceed. For example, if we want to assign the


same color to the categories below
To do this, first highlight 'Users and Groups' and
invoke the inspector

► <div id="header"> </div> flex text-transform: uppercase;


<!-- END Header --> }
T<div class="main" id="main"> flex .module h2, .module caption, .inline-group h2 {
T<div class="content"> margin:► 0;
padding: ► 8px;
<!-- Content -->
font-weight: 400;
T<div id="content" class="colMS">
font-size: 0.8125rem;
<hl>AAMMHMCTpnpoBaHMe caitTa</hl> text-align: left;
T<div id="content-main"> background: ► ■var(--primary);
T<div class="app-auth module"> color: Dvar(- -header-link-color);
T<table> }
►<caption> — </caption> »« $0 caption { user
► <tbody> ••• </tbody> dispiay: table-caption;
</table> text-align:—webk-i t - center;

On the right, in the styles, we see the line:

.module hlj .module caption^ .inline-group h2 {

Copy .module caption and paste it into our styles

#header, .module caption {


background: #088A08;
}

Sheader, .module caption -{


background: S088AG8;
3 }
Refresh the page in the browser

Keep in mind that pages are cached, so it's best to


refresh using the Ctrl + F5 key combination. In this
case, the cache will be cleared.
This way, you can change the design and color scheme
of our admin panel. If we need to change the actual
title...

Django administration

it's better to do it through admin.py. Let's add two


additional attributes

admin.site.site_title = 'Admin Panel for Travels'


admin.site.site_header = 'Travel Site Management'
Refresh the page

The full list of these attributes can be found in the


documentation. Let's make the display of our
thumbnails directly in the list
□ ID 1EAD1NG
2 * TIME OF CREATION 1 - PHO™ RUB LI CATION

□ 18 Tunisia December 27,2022 10:24 am photos/2022/12/27/tunisia.jpg □


o 17 Tanzania December 25,2022 10:44 am photos/2022/12/25/tanzaniajpg □
□ 16 Netherlands December 18,2022 12:19 pm photos/2022/12/18/Nederlandjpg □

To do this, in the admin.py file, find the


Dir_travelAdmin class and add a special method that
returns HTML code. We will then use this code instead
of paths

photos/2022/12/27/tu ni si a j pg

p hotos/2022/12/25/ta nzani a jpg

def get_html_photo(self, object):


return mark_safe(f"<img
src='{oblect.photo.url}' width=50>")

Rclass Dir_travelAdmin(admin.ModelAdmin) :
7 ©T list_display = ('id', 'title', 'time_create', 'photo', 'is_pubtished')
8 Of list_disptay_links = ('id', 'title')
9 ©T search_fields = ('title', 'content')
10 ©T list_editable = ('is_published',)
11 ©t list_filter = ('is_published', 'time_create')
12 ©T prepopulated_fields = {"slug": ("title",)}

def get_html_photo(self, object):


15 * return mark_safe(f"<img src='-(object.photo.url}' width=50>")
16

object - a parameter that will refer to the current entry


in the list.
mark_safe - a function that indicates not to escape
the tags "<img src='{object.photo.url}'
width=50>"). This function needs to be imported.
from django.utils.safestring import mark_safe
f®)m django.contnb import admin
from django.utils.safestring import mark_safe

Next, in the line


list_display = ('id', 'title', 'time_create', 'photo',
'is_published')
replace 'photo' with 'get_html_photo
list_display = ('id', 'title', 'time_create', 'get_html_photo',
'is_published')
Not all of our posts have a photo. Accordingly, we can
encounter an exception.
To prevent this, let's add one more line
if object.photo:
return mark safe(f"<img src='{object.photo.url}'
width=50>")

Rclass Dir_travelAdmin(admin.ModelAdmin):
8 ©t list_display = ('id1, 'title', 'time_create', 'get_html_photo’, 'is_published')
9 O| list_display_links = ('id', 'title')
10 ©T search_fields = ('title', 'content')
11 Of | list_editable = (' is_published1,)
12 Of list_filter = ('is_published', 'time_create1)
13 ©T prepopulated_fields = {"slug": ("title",)}

def get_html_photo(self, object):


if object.photo:
17 ? return mark_safe(f"cimg src='{object.photo.url}' width=50>")

Go to the site and refresh the page. If everything is


done correctly, we should see thumbnails
Action: --------- Selected 0 items out of 16

□ HEADING 2 TIME OF CREATION GET HTML PHOTO PUBLICATION

O 18 Tunisia December 27,2022 10:24 am □


□ 17 Tanzania December 25, 2022 10:44 am □

□ 16 Netherlands December 18, 2022 12:19 pm □


□ 13 Tanzania December 2, 2022 1 2:00 pm □
□ 12 Zimbabwe December 2, 2022 11:58 am □

If there are no images in any post, we will see dashes.


Let's make it so that instead of showing 'GET HTML
PHOTO', the thumbnail is displayed.
To do this, let's add another attribute in our class.
get_html_photo.short_description = " Thumbnail "

def get_htmL_photo(self, object):


if object.photo:
return mark_safe(f"<img src='{object.photo.url}' width=5G>")
18
19 ® get_html_photo. short_description = "Miniature"

After refreshing the page, we'll see...

MINIATURE PUBLICATION

Such transformations can be done not only with photos


but with any other information as well. Let's display
photos even during editing.
To do this, in the same Dir_travelAdmin class, add the
following attribute:
fields = ('title', 'slug', 'cat', 'content', 'photo',
'is_published')

12 ©r list_filter = ('is_published ', ' time_create')


13 ©r prepopulated_fields = {"slug": ("title",)}
14 ©t = fields = ('title', 'slug1, 'cat', 'content', 'photo', 'is_published')

This attribute contains the order and list of editable


fields.
Let's also add non-editable, read-only fields.
readonly_fields = ('time_create', 'time update')
And only after that, we can specify them in the
collection fields
fields = ('title', 'slug', 'cat', 'content', 'photo', 'is_published',
'timecreate', 'time update')

14 ®t fields - ('title', 'slug', 'cat1, 'content', 'photo', 'is_published', 'time_create', 'time_update')


15 <d| ® readonly_fields = (1time_create', 'time_update')

Let's refresh our page.

s Publication

Time of creation: December 27, 2022 10:24 am

Change time: December 27, 2022 10:24 am

Let's add a thumbnail that will be a read-only field.


As we already know, in the readonly_fields attribute,
add the name of our method with the photo:
readonly_fields = ('time_create', 'time_update',
'get_html_photo'),
and then add this thumbnail after the 'photo' field
fields = ('title', 'slug', 'cat', 'content', 'photo',
'get_html_photo', 'is_published', 'time_create',
'time_update')

14 ©t fields = ('title', 'slug', 'cat1, 'content1, 'photo', 'get_html_photo', 'is_published', 'time_create', 'time_update')
15 ©t readonly_fields = ('time_create', 'time_update', 'get_html_phato')

Next, refresh the page


Photo: At the moment: photos/2022/12/27/tunisia.jpg

Change: | Choose File | File not selected

Miniature:

□ Publication

Time of creation: December 27, 2022 10:24 am

Change time: December 27, 2022 10:24 am

As we can see, everything is working.


You can find more details about all the attributes in
the documentation provided above.

Alright, that's probably it!

You will need to deploy (publish) your site on a so-


called production server. Practice this on your own. I
can recommend an excellent server,
PythonAnywhere, where you can deploy your site
quite easily and for free (with some limitations).
The information provided should be more than enough
for a good and reliable start.

For confident growth in this industry, you will need


additional resources and daily hard work, overcoming
laziness and, at times, despair. But if you see a goal in
front of you, go towards it without turning aside. Your
hard work will be rewarded. May the force be with
you!

Volodymyr Zadorozhnyi © 2023

You might also like