Простейшее пособие по Django и Python

 

Автор делал пособие для себя, чтоб вспомнить основы и азы.

Версия Django на момент написания 4.1

Пособие дает простейшее представление с нуля до простешего 3х страничного сайта без всякой динамики и прочего.

Немного навигации, немного ORM, самые основы, не более.

 

Запуск django под windows

 

В windows powershell от администратора делаем алиасы на установленный python:

Set-Alias python "C:\Users\Your_UserName\AppData\Local\Programs\Python\Python310\python.exe"

Set-Alias pip "C:\Users\Your_UserName\AppData\Local\Programs\Python\Python310\scripts\pip.exe"

 

Разрешаем выполнение сценариев:

Set-ExecutionPolicy Unrestricted

 

СОздаем виртуальное окружение и далее работаем в нем:

cd C:\\python\django

python -m venv site1

.\site1\Scripts\activate

 

Установка и запуск проекта:

(site1) PS C:\python\django\site1\app> pip install django

(site1) PS C:\python\django\site1\app> django-admin.exe startproject app

(site1) PS C:\python\django\site1\app> cd app

(site1) PS C:\python\django\site1\app> python manage.py runserver 0.0.0.0:8000

 

Проверяем:

http://127.0.0.1:8000/

 

 

Приложение

 

Создадим отдельную, самостоятельную часть сайта

Например - форум, опросник, основное приложение, чат, подсайт и т.п.

Такие части называются приложениями

 

Создадим базовый функционал сайта с названием "people":

(site1) PS C:\python\django\site1\app> python manage.py startapp people

 

Создастя каталог приложения:

C:\python\django\site1\app\people

 

Файл указывает, что приложение надо рассматривать как пакет:

app/people/__init__.py

 

Папка для миграций нашего приложения:

app/people/migrations

 

Связь с админкой сайта:

app/people/admin.py

 

Для хранения ORM моделей, представления данных из БД:

app/people/models.py

 

Модуль для тестирующих процедур:

app/people/tests.py

 

Модуль представлений (контроллеров):

app/people/views.py

 

Приложение надо зарегистрировать, чтоб проект знал о нем

 

Приложения прописываюстя тут:

C:\python\django\site1\app\app\settings.py

...

INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'people', # <-- Добавить строку

]

...

 

Django, обращаясь к пакету people идет в:

C:\python\django\site1\app\people\apps.py

...и находит в нем class PeopleConfig

 

Упростим дальнейшую работу с классом:

...

INSTALLED_APPS = [

    ...

    'people.apps.PeopleConfig', # <-- Добавить строку

]

...

 

Сделаем обработчик главной страницы приложения

 

Можно сделать в виде функции или класса

 

С помощью функции в app/people/views.py:

from django.http import HttpResponse

from django.shortcuts import render

 

def index(request):

    return HttpResponse("Главная страница")

 

На входе запроса:

Переменная request - ссылка на класс HttpRequest

Через request будет доступна информация  по данному запросу

 

На выходе запроса:

from django.http import HttpResponse - импортируем класс

Экземпляр класса HttpResponse - формирует ответ

 

Функцию с именем index надо связать с url адресом в

C:\python\django\site1\app\app\urls.py

...

from people.views import index

 

urlpatterns = [

    path('admin/', admin.site.urls),

    path('people/', index), # http://0.0.0.0:8000:/people/

]

 

Мы импортировали фугкцию index из представления приложения people

Далее мы связали функцию с маршрутизацией

 

Проверяем:

http://127.0.0.1:8000/people/

 

Мы сделали:

1. app/app/settings.py <- app/people/apps.py - связь проекта и приложения

2. app/app/urls.py <- app/people/views.py - связь маршрутизации и представления

 

Добавим функционал

 

В app/people/views.py:

from django.http import HttpResponse

from django.shortcuts import render

 

def index(request):

    return HttpResponse("Главная страница")

 

def categories(request):

    return HttpResponse("<h1>Категории статей</h1>")

 

В app/app/urls.py:

...

from people.views import *

 

urlpatterns = [

    path('admin/', admin.site.urls),

    path('', index), # http://0.0.0.0:8000:/

    path('ct/', categories), # http://0.0.0.0:8000:/ct/

]

 

import * - импортировать сразу все из people.views

path('', index) - переделали маршрутизацию главной страницы

path('ct/', categories) - добавили маршрутизацию

 

Проверяем - http://127.0.0.1:8000/ct/

 

 

Перепишем функционал на более правильный и удобный

 

В app/app/urls.py:

...

from people.views import *

from django.urls import path, include

 

urlpatterns = [

    path('admin/', admin.site.urls),

    path('people/', include('people.urls')),

]

 

С помощью функционала include мы импортировали файл urls прилоржения people

 

В app/people/urls.py:

from django.urls import path

from .views import *

 

urlpatterns = [

    path('', index),  # http://0.0.0.0:8000:/people/

    path('ct/', categories),  # http://0.0.0.0:8000:/people/ct/

]

 

Проверяем:

http://127.0.0.1:8000/people/

http://127.0.0.1:8000/people/ct/

 

Мы сделали связи:

1. app/app/settings.py <- app/people/apps.py - проекта и приложения

2. app/app/urls.py <- app/people/urls.py - маршрутизаций проекта и приложения

2. app/people/urls.py <- app/people/views.py - маршрутизации и представления

 

Добавим динамическую маршрутизацию

 

В app/app/urls.py делаем заглавную страницу на приложение:

...

urlpatterns = [

    path('admin/', admin.site.urls),

    path('', include('people.urls')),

]

 

В app/people/urls.py добавим числовые подзапросы в url:

from django.urls import path, re_path

from .views import *

 

urlpatterns = [

    path('', index),  # http://0.0.0.0:8000:/people/

    path('ct/<int:catid>/', categories),  # http://0.0.0.0:8000:/people/ct/число/

    re_path(r'^arch/(?P<year>[0-9]{4})/', arch), # http://127.0.0.1:8000/arch/год/

]

 

Здесь <int:catid>:

int - тип параметра

catid - произвольное имя параметра, которым мы сможем оперировать

 

По умолчанию доступны следующие конвертеры пути:

 

Доступные типы переменных:

str - непустая строка

int - ноль или любое положительное цело

slug - строка заголовка, состоящей из букв или цифр ASCII, дефиса, подчеркивания

Пример: build-your-1st-django-site

uuid - форматированный UUID

Пример: 075194d3-6885-417e-a8a8-6c931e272f00

path - любая непустая строка включая разделитель пути, '/'

 

Подробнее в актуальной документации, ищем "URL dispatcher":

https://docs.djangoproject.com/en/4.1/topics/http/urls/

 

re_path - позволяет создать регулярное выражение для url

arch - часть url

<year> - имя переменной

Все остальное - шаблон регулярного выражения (4 числа, формат года)

 

В app/people/views.py пропишем параметр из app/people/urls.py:

...

def categories(request, catid):

    get = ''

    if(request.GET):

        print(request.GET)

        for key in request.GET:

            get = get + key + ' ->' + request.GET[key] + ', '

    else:

        get = "ничего не передали"

    return HttpResponse(f"<h1>Категории статей</h1>"

                        f"<p>{catid}</p>"

                        f"<p>GET часть: {get}</p>"

                        f"")

 

def arch(request, year):

    return HttpResponse(f"<h1>Архив статей</h1><p>За {year} год</p>")

 

В categories мы добавили функционал работы с GET запросом

 

Функционал имеется в переменной request в GET

Получаем словарь GET

Gеребираем все его значения и ключи

Выводим в консоль и браузер

Делается проверка, передавалось вообще что-то с помощью GET

 

Проверяем:

http://127.0.0.1:8000/ct/1/

http://127.0.0.1:8000/arch/2022/

http://127.0.0.1:8000/ct/1/?var1=test1&var2=test2

 

Отключим дебаг для несуществующих страниц - ошибка 404

 

Правим app/app/settings.py:

DEBUG = False

ALLOWED_HOSTS = ['*']

 

Теперь нам доступны спецобработчики:

handler400

handler403

handler404

handler500

 

Прописываем обработку специальной переменной в app/app/urls.py:

handler404 = pageNotFound

 

Добавляем в app/people/views.py:

from django.http import HttpResponse, HttpResponseNotFound, Http404

 

def categories(request, catid):

    if int(catid) > 7:

        raise Http404()

 

def pageNotFound(request, exception):

    return HttpResponseNotFound('<h1>Запрошенная страница не существует</h1>')

 

Если число категории больше 7 - задействуем обработку 404 ошибки

 

Проверьте - http://127.0.0.1:8000/ct/9/

 

Вместо 404 ошибки сделаем редирект

 

В app/people/urls.py добавим имя, к которому мы будем маршрутизировать редирект:

...

urlpatterns = [

    path('', index, name='red_url'),  # http://0.0.0.0:8000:/people/

...

]

...

 

Изменяем в app/people/views.py:

from django.shortcuts import render, redirect

 

def categories(request, catid):

    if int(catid) > 7:

        # 302 code

        #return redirect('red_url')

        # 301 code

        return redirect('red_url', permanent=True)

 

О миграциях и бд

 

Настройки, к какому типу БД обращается Django указываются в settings.py

 

По умолчанию это sqlite3(указан драйвер и путь к файлу БД):

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.sqlite3',

        'NAME': BASE_DIR / 'db.sqlite3',

    }

}

 

Для получения визуального доступа к БД можете использовать:

https://sqlitestudio.pl/

 

У меня БД находится тут:

C:/python/django/site1/app/db.sqlite3

 

В django мы описываем ORM модели на базе классов в models.py

 

Опишем модель (таблицу в БД) тут app/people/models.py:

from django.db import models

 

class People(models.Model):

    name = models.CharField(max_length=50)

    biography = models.TextField(blank=True)

    birthday = models.DateField(null=True)

    photo = models.ImageField(upload_to="photos/%Y/%m/%d/")

    publish = models.BooleanField(default=True)

 

Типы полей описаны в документации:

https://docs.djangoproject.com/en/4.1/topics/db/models/

https://docs.djangoproject.com/en/4.1/topics/db/models/#field-types

 

Самостоятельно разберитесь, что за типы и параметры в таблице

 

Для поля ImageField необходимо сделать донастройку:

https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.ImageField

https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-MEDIA_ROOT

https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-MEDIA_URL

 

Доустановите необходимый для ImageField пакет Pillow:

PS C:\python\django\site1\app> python -m pip install Pillow

 

app/app/settings.py добавьте:

import os

from pathlib import Path

...

DEBUG = True

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

MEDIA_URL = '/media/'

 

Добавим еще один маршрут, в случае включения константы дебага settings.DEBUG

 

app/app/urls.py:

from django.conf.urls.static import static

from app import settings

...

if settings.DEBUG:

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

 

Т.е. мы сделали:

app/people/models.py - описали таблицу для БД

app/app/settings.py - настроили дебаг и url

app/app/urls.py - включили обработку маршрута, когда включен дебаг в settings.py

 

Миграции - модули python, при выполнении создаются новые или изменяются таблицы

 

Папка миграций нашего приложения people:

app/people/migrations

 

Создадим файл миграций:

(site1) PS C:\python\django\site1\app> python manage.py makemigrations

Migrations for 'people':

  people\migrations\0001_initial.py

    - Create model People

 

Посмотрим sql запрос, который выполнится файлом 0001_initial.py приложения people:

(site1) PS C:\python\django\site1\app> python manage.py sqlmigrate people 0001

BEGIN;

--

-- Create model People

--

CREATE TABLE "people_people" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(50) NOT NULL,

 "biography" text NOT NULL, "birthday" date NOT NULL, "photo" varchar(100) NOT NULL, "publish" bool NOT NULL);

COMMIT;

 

Выполним миграцию в БД:

(site1) PS C:\python\django\site1\app> python manage.py migrate

 

CRUD

 

Create, Read, Update, Delete операции с таблицами в БД

 

Войдем в shell:

(site1) PS C:\python\django\site1\app> python manage.py shell

Python 3.10.8 (tags/v3.10.8:aaaf517, Oct 11 2022, 16:50:30) [MSC v.1933 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

(InteractiveConsole)

 

Импортируем из приложения app модель People

>>> from people.models import People

 

Запишем значения в строку, id - автоматический:

>>> People(name='Andy', biography='Born in USSR')

<People: People object (None)>

 

Сохраним все действия в переменную:

>>> tr1= _

 

Методом save сохраним данные в таблицу:

>>> tr1.save()

 

Назначенный id записи:

>>> tr1

<People: People object (1)>

 

Узнаем его id:

>>> tr1.pk

1

 

Обратимся к какому-либо полю:

>>> tr1.name

'Andy'

 

Посмотрим sql запросы после выполнения команд:

>>> from django.db import connection  

 

>>> connection.queries

[{'sql': 'INSERT INTO "people_people" ("name", "biography", "birthday", "photo", "publish") VALUES (\'Andy\', \'Born in USSR\', NULL, \'\', 1) RETURNING "people_people"."id"', 'time': '0.016'}]

 

>>> tr2 = People(name='Boris', biography='Born in RSFSR')     

 

>>> tr2.save()

 

>>> connection.queries                                   

[{'sql': 'INSERT INTO "people_people" ("name", "biography", "birthday", "photo", "publish") VALUES (\'Andy\

', \'Born in USSR\', NULL, \'\', 1) RETURNING "people_people"."id"', 'time': '0.016'}, {'sql': 'INSERT INTO

 "people_people" ("name", "biography", "birthday", "photo", "publish") VALUES (\'Boris\', \'Born in RSFSR\', NULL, \'\', 1) RETURNING "people_people"."id"', 'time': '0.000'}]

 

Еще вариант добавления записи:

>>> tr3 = People()

>>> tr3.name = 'Candy'

>>> tr3.biography = 'Born in SNG'

>>> tr3.save()

 

Еще варианты добавления записи, метод save уже не нужен:

>>> tr4 = People.objects.create(name='Drago', biography='Born in RF')

>>> People.objects.create(name='Batya', biography='Born in BR')

 

Теперь сделаем выборку

 

app/people/models.py добавим:

...

class People(models.Model):

...

    def __str__(self):

        return self.name

 

Это нужно, чтоб вместо заголовков object:

>>> People.objects.all()                                       

<QuerySet [<People: People object (1)>, <People: People object (2)>, <People: People object (3)>, <People: People object (4)>, <People: People object (5)>]>

 

Отображалась нужная информация, перезайдите:

>>> exit()

(site1) PS C:\python\django\site1\app> python manage.py shell

 

>>> from people.models import People

 

>>> People.objects.all()                                              

<QuerySet [<People: Andy>, <People: Boris>, <People: Candy>, <People: Drago>, <People: Batya>]>

 

Присвойте переменное последние результаты:

>>> w = _

 

Обратитесь к записям:

>>> w[0]

<People: Andy>

 

>>> w[0].biography

'Born in USSR'

 

КОличество записей:

>>> len(w)

5

 

Перебор записей:

>>> for i in w: print(i.name)

Andy

Boris

Candy

Drago

Batya

 

Выборка определенной записи по условию:

>>> People.objects.filter(name='Andy')

>>> People.objects.filter(pk=3)

 

Выборка по условию __gte >= или __lte <=:

>>> People.objects.filter(pk__gte=4)

>>> People.objects.filter(pk__lte=2)

 

Выборка определенной записи по обратному условию:

>>> People.objects.exclude(pk=3)

 

Выбирает только одну запись, если будет больше - вернет ошибку:

>>> People.objects.get(pk=3)

 

Сортировка выборки:

>>> People.objects.filter(pk__gte=2).order_by('name')

 

Изменение записис из метода filter:

>>> People.objects.filter(pk=3).update(name='Corny')

 

Изменение записис из метода get:

>>> u = People.objects.get(name='Andy')     

>>> u.name = 'Anna'                      

>>> u.save()

 

Удаление записей:

>>> d = People.objects.filter(pk__gte=4)

>>> d.delete()

 

Шаблоны

 

Если посмотреть app/people/views.py обнаружим import render

 

render - функция шаблонизатора

 

Создадим подкаталоги и файл html

 

app/people/templates/people/index.html:

<!DOCTYPE html><html lang="en"><head>

<meta charset="UTF-8"><title>Title</title>

</head><body></body></html>

 

app/people/views.py пропишем путь:

...

def index(request):

    return render(request, 'people/index.html')

...

 

Указывать templates не надо т.к. в app/app/settings.py:

TEMPLATES = [

...

        'APP_DIRS': True,

...

 

Уже указано искать шаблоны в подпапках приложений

 

Создадим еще один шаблон app/people/templates/people/about.html:

<!DOCTYPE html><html lang="en">

<head>

<meta charset="UTF-8"><title>{{title}}</title>

</head>

<body><h1>{{about}}</h1></body></html>

 

{{title}} и {{about}} - переменные, передадим их разными способами из views.py

 

Создадим представление для шаблона app/people/views.py:

...

ix = {'Главная': '/', 'Статьи по категориям': '/ct/1/', 'Архив': '/arch/2022/', 'Информация': '/about/'}

 

def index(request):

    return render(request, 'people/index.html', {'ix': ix, 'title': 'Главная страница'})

 

def about(request):

    return render(request, 'people/about.html', {'title': 'Информация', 'about': 'О компании'})

...

 

И маршрут для about app/people/urls.py:

...

urlpatterns = [

...

    path('about/', about, name='about')

]

...

 

Переберем переданный словарь ix из views.py:

app/people/templates/people/index.html:

<!DOCTYPE html><html lang="en"><head>

<meta charset="UTF-8"><title>{{title}}</title>

</head><body>

<ul>

{% for key, value in ix.items %}

<li><a href="{{ value }}"> {{ key }} </a></li>

{% endfor %}

</ul>

</body></html>

 

Проверяем - http://127.0.0.1:8000/

Проверяем - http://127.0.0.1:8000/about/

 

Создадим базовый шаблон, избавимся от хардконинга html

 

app/people/templates/people/base.html:

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>{{ title }}</title>

</head>

<body>

{% block body %}

{% endblock %}

</body>

</html>

 

Будем передавать контент в блок body базового шаблона

 

index.html меняем на:

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

 

{% block body %}

<ul>

{% for key, value in ix.items %}

<li><a href="{{ value }}"> {{ key }} </a></li>

{% endfor %}

</ul>

{% endblock %}

 

about.html меняем на:

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

 

{% block body %}

<h1>{{about}}</h1>

{% endblock %}

 

Хранить значение нужно в БД

 

Для этого перепишем импорт значений из нашей таблицы

 

app/people/views.py:

from .models import *

...

def about(request):

    people = People.objects.all()

    return render(request, 'people/about.html', {'people': people, 'title': 'Информация', 'about': 'О компании'})

 

about.html меняем на:

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

 

{% block body %}

<h2>Наши сторудники</h2>

<ul>

    {% for p in people %}

    <li>{{p.name}}: {{p.biography}}</li>

    {% endfor %}

</ul>

{% endblock %}

 

Статические файлы

 

Пропишите в app/app/settings.py:

STATIC_URL = 'static/'

STATIC_ROOT = os.path.join(BASE_DIR, 'static')

STATICFILES_DIRS = []

 

Имеем структуру:

site1

-app

--app

---static - нестандартный путь, переменная STATICFILES_DIRS

--people

---static - нестандартный путь, переменная STATICFILES_DIRS

----people

-----js

-----css

------styles.css

-----images

--static - стандартный путь проекта, переменная STATIC_ROOT

 

Переменная STATIC_URL отвечает за префикс URL статических файлов

 

app/people/static/people/css/styles.css:

h2 { color: green; text-decoration: underline; }

 

Положим файл:

app/people/static/people/images/dj.jpg

 

Создать стандартную папку static, которая включит все подключенные папки:

(site1) PS C:\python\django\site1\app> python manage.py collectstatic

 

Подключим статику в базовый шаблон app/people/templates/people/base.html:

{% load static %}

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>{{ title }}</title>

    <link type="text/css" href="{% static 'people/css/styles.css' %}" rel='stylesheet' />

</head>

<body>

<h2>Site powered by</h2>

<a href="{% url 'red_url' %}"><img src="{% static 'people/images/dj.jpg' %}" alt="dj_image"></a>

{% block body %}

{% endblock %}

</body>

</html>

 

Так-же воспользуемся тегом url к имени из app/people/urls.py в ссылке <a href>

 

Фильтры

 

Для манипуляции с данными, поступающими в шаблоны используются встроенные фильтры:

https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#built-in-filter-reference

 

На примере обрезающего текс по длине фильтра

 

Изменим в app/people/templates/people/about.html:

...

<li>{{p.name}}: {{p.biography|truncatechars:10}}</li>

..

 

Мы обезали вывод текста из biography

 

Немного упростим формирование url

 

В app/people/views.py заменим длинный список в render

...

def about(request):

    people = People.objects.all()

    data = {

        'people': people,

        'title': 'Информация',

        'about': 'О компании'

    }

    return render(request, 'people/about.html', context=data)

...

 

Ключевое слово context дает указать словарь данных

 

Добавим в app/people/models.py:

...

class Article(models.Model):

    category = models.IntegerField(blank=False)

    name = models.CharField(max_length=50)

    content = models.TextField(blank=True)

    author = models.ForeignKey('People', on_delete=models.CASCADE, null=True)

 

    def __str__(self):

        return self.name

...

 

Связь между таблицами People(id) - Article(author_id)

 

models.CASCADE:

при удалении записи из первичной модели (People)

удаляться все связанные записи из вторичной модели (Article)

 

Документация по созданию связей между моделями:

https://docs.djangoproject.com/en/4.1/topics/db/models/#relationships

 

Добавим записей

 

(site1) PS C:\python\django\site1\app> python manage.py makemigrations

 

(site1) PS C:\python\django\site1\app> python manage.py migrate

 

(site1) PS C:\python\django\site1\app> python manage.py shell

 

>>> from people.models import Article

 

Article.objects.create(category=1, name='Auto tunung', content='Any content in article', author_id=1)

Article.objects.create(category=1, name='Moto tunung', content='Any content in article', author_id=1)

Article.objects.create(category=2, name='Air tunung', content='Any content in article', author_id=2)

Article.objects.create(category=2, name='Spring tunung', content='Any content in article', author_id=2)

 

Делаем шаблон people/templates/people/ct.html:

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

 

{% block body %}

<h2>Категории статей</h2>

<ul>

    {% for a in article %}

    <li><a href="{% url 'post_url' a.pk %}">{{a.name}}</a> Автор: {{a.author.name}}</li>

    {% endfor %}

</ul>

{% endblock %}

 

post_url - имя маршрута из app/people/urls.py

a.author.name - значение по связанной таблице

 

Изменим app/people/views.py, добавив обработчик запроса url статьи:

...

def categories(request):

    article = Article.objects.all()

    data = { 'article': article }

    return render(request, 'people/ct.html', context=data)

 

def show_post(request, post_id):

    return HttpResponse(f"Отображение статьи с id = {post_id}")

...

 

Маршрутизацию привяжем к имени post_url app/people/urls.py:

...

    path('ct/', categories),  # http://0.0.0.0:8000:/ct/

    path('ct/<int:post_id>/', show_post, name='post_url'),  # http://0.0.0.0:8000:/ct/число/

...

 

Проверяем:

http://127.0.0.1:8000/ct/

http://127.0.0.1:8000/ct/3/

 

Второй вариант

 

Правим app/people/models.py:

...

from django.urls import reverse

...

class Article(models.Model):

    category = models.IntegerField(blank=False)

    name = models.CharField(max_length=50)

    content = models.TextField(blank=True)

    author = models.ForeignKey('People', on_delete=models.CASCADE, null=True)

 

    def __str__(self):

        return self.name

 

    def get_url(self):

        return reverse('post_url', kwargs={'post_id': self.pk})

 

Изменяем шаблон people/templates/people/ct.html:

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

 

{% block body %}

<h2>Категории статей</h2>

<ul>

    {% for a in article %}

    <li><a href="{{ a.get_url }}">{{a.name}}</a> Автор: {{a.author.name}}</li>

    {% endfor %}

</ul>

{% endblock %}

 

ct.html выводит список статей

В url статьи вызывается обработчик с именем post_url из urls.py

Обработчик post_url вызывает функцию show_post с переданной ей post_id из views.py

 

Перепишем обработчик app/people/views.py:

...

def show_post(request, post_id):

    article = Article.objects.get(pk=post_id)

    return HttpResponse(f"Отображение статьи с id = {post_id}, Именем: {article.name}<br>{article.content}")

...

 

Тут мы выбрали данные из связанной таблицы и вывели их

 

В app/people/views.py подправим вывод на главной ссылок:

ix = {'Главная': '/', 'Статьи по категориям': '/ct/1/'

Заменим на:

ix = {'Главная': '/', 'Статьи по категориям': '/ct/'

 

Админка

 

Русифицируем в app/app/settings.py:

LANGUAGE_CODE = 'ru'

 

Создадим суперюзера для админки:

(site1) PS C:\python\django\site1\app> python manage.py createsuperuser

 

Проверяем:

http://127.0.0.1:8000/admin/login/?next=/admin/

 

Импортируем приложения в админку app/people/admin.py:

from django.contrib import admin

from .models import *

 

class PeopleAdmin(admin.ModelAdmin):

    list_display = ('id', 'name', 'biography', 'birthday')

    list_display_links = ('id', 'name')

    search_fields = ('name', 'biography')

    list_editable = ('biography', 'birthday')

 

admin.site.register(People, PeopleAdmin)

 

class ArticleAdmin(admin.ModelAdmin):

    list_display = ('id', 'name', 'content', 'author')

    list_display_links = ('id', 'name')

    search_fields = ('name', 'content')

    list_editable = ('content', 'author')

    list_filter = ('name', 'author')

 

admin.site.register(Article, ArticleAdmin)

 

В классах ...Admin мы прописали отображение стобцов и поиск в админке

 

list_editable - доступные для редактирования значения в списке вывода админки

 

list_filter - фильтр вывода в админке по критериям

 

Перепишем app/people/models.py:

...

class People(models.Model):

...

name = models.CharField(max_length=50, verbose_name='Имя')

...

class Meta:

        verbose_name = 'Авторы'

        verbose_name_plural = 'Авторы'

        ordering = ['name']

...

class Article(models.Model):

...

name = models.CharField(max_length=50, verbose_name='Название')

...

    def get_absolute_url(self):

        return reverse('post_url', kwargs={'post_id': self.pk})

 

    class Meta:

        verbose_name = 'Статьи'

        verbose_name_plural = 'Статьи'

        ordering = ['author', 'name']

...

 

verbose_name='Название\Имя' - отображение в админке внутри модели

 

Применим изменения:

(site1) PS C:\python\django\site1\app> python manage.py makemigrations

(site1) PS C:\python\django\site1\app> python manage.py migrate

 

 

Перепишем app/people/templates/people/ct.html:

<li><a href="{{ a.get_url }}"

меняем на

<li><a href="{{ a.get_absolute_url }}"

 

Отображение раздела приложения People в админке меняем в app/people/apps.py:

from django.apps import AppConfig

 

class PeopleConfig(AppConfig):

    default_auto_field = 'django.db.models.BigAutoField'

    name = 'people'

    verbose_name = 'Авторы и статьи'

 

Заметьте, в админке слева Peoples изменилось на Статьи

 

ordering - сортировка в выводе по столбцам

 

В записи админки приложения Articles увидите справа "СМОТРЕТЬ НА САЙТЕ":

http://127.0.0.1:8000/admin/people/article/4/change/

 

Добвать ДР и фото у авторов с помощью админки:

http://127.0.0.1:8000/admin/people/people/1/change/

http://127.0.0.1:8000/admin/people/people/2/change/

http://127.0.0.1:8000/admin/people/people/3/change/

 

Подправим вывод статьи для вывода фото автора app/people/views.py:

...

def show_post(request, post_id):

    article = Article.objects.get(pk=post_id)

    return HttpResponse(f"Отображение статьи с id = {post_id}, "

                        f"Именем: {article.name}"

                        f"<br>{article.content}"

                        f"<br>Фото автора:"

                        f"<br><img src='{article.author.photo.url}'>")

 

Теги шаблонов

 

simple_tag: обрабатывает данные и возвращает строку

inclusion_tag: обрабатывает данные и возвращает отображаемый шаблон

assignment_tag: обрабатывает данные и устанавливает переменную в контексте

 

Мы рассмотрим первый прмиер с simple_tag

 

Создадим структуру:

people/

      __init__.py

      models.py

      ...

      templatetags/

          __init__.py

          custom_tags.py

 

Файл указывает, что приложение надо рассматривать как пакет:

__init__.py

 

Модуль (custom_tags.py ) должен содержать переменную с именем register

register является экземпляром библиотеки шаблонов, в котором зарегистрированы все теги

 

Создадим простой тег, который будет подсчитывать % выведенных статей

 

custom_tags.py:

from django import template

register = template.Library()

from people.models import Article, People

 

@register.simple_tag

def count_articles(article_pk):

    return (article_pk*100)/Article.objects.count()

 

Добавим в шаблон использование модуля custom_tags.py в ct.html:

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

{% load custom_tags %}

{% block body %}

<h2>Категории статей</h2>

<ul>

    {% for a in article %}

    <li><a href="{{ a.get_absolute_url }}">{{a.name}}</a>

        Автор: {{a.author.name}}

        % выведенных статей: {% count_articles a.pk %}

    </li>

    {% endfor %}

</ul>

{% endblock %}

 

Определим желаемое имя тега

 

Перепишем custom_tags.py:

...

@register.simple_tag(name='ct_as')

...

 

Перепишем ct.html:

...

        % выведенных статей: {% ct_as a.pk %}

...

 

Добавим еще тэг app/people/templatetags/custom_tags.py:

...

@register.simple_tag()

def get_authors():

    return People.objects.all()

...

 

Однако значения тэга нельзя перебрать

Для этотго существует ключевое слово as (ссылка на данные)

ct.html:

...

{% get_authors as gt_as %}

<p>Наши авторы:

{% for a in gt_as %} {{a.name}} {% endfor %}

</p>

...

 

Вариант inclusion_tag - возвращающий шаблон

 

Перепишем app/people/templatetags/custom_tags.py:

...

@register.inclusion_tag('people/show_authors.html')

def get_authors():

    authors = People.objects.all()

    return {"authors": authors}

...

 

Перпешем кусок из ct.html в app/people/templates/people/show_authors.html:

<p>Наши авторы:

{% for a in authors %} {{a.name}} {% endfor %}

</p>

 

authors в show_authors.html - return {"authors": authors} из custom_tags.py

 

Вызовем тэг app/people/templates/people/ct.html:

...

{% get_authors %}

...

 

Добавим тэг с условием выборки в app/people/templatetags/custom_tags.py:

...

@register.simple_tag()

def get_author(author):

    a = People.objects.filter(name=author).get()

    return f", ДР: {a.birthday}"

...

 

Перепишем app/people/templates/people/show_authors.html:

{% load custom_tags %}

<p>Наши авторы:

{% for a in authors %}

    {{a.name}} {% get_author a.name %}

{% endfor %}

</p>

 

Slug - слаг

 

Фрагмент URL, состоящий из латинских букв, цифр, символов подчеркивания и дефиса

 

Добавим поле в модель app/people/models.py и возврат абсолютного url:

...

class Article(models.Model):

    category = models.IntegerField(blank=False)

    name = models.CharField(max_length=50, verbose_name='Название')

    content = models.TextField(blank=True)

    author = models.ForeignKey('People', on_delete=models.CASCADE, null=True)

    slug = models.SlugField(max_length=50, unique=True, db_index=True, verbose_name="URL")

...

    def get_absolute_url(self):

        return reverse('post_url', kwargs={'post_slug': self.slug})

...

 

Удалите файл бд и файлы миграции, иначе модель не пересоберется

 

Чтоб не заполнять слаг руками, скорректируйте app/people/admin.py:

...

class ArticleAdmin(admin.ModelAdmin):

...

    prepopulated_fields = {"slug": ("name",)}

...

 

Это даст формирование слага на основе значения в name

 

Скорректируем маршрутизация для слага app/people/urls.py:

...

path('ct/<slug:post_slug>/', show_post, name='post_url'),

...

 

Скорректируем вывод страницы app/people/views.py:

def show_post(request, post_slug):

    article = Article.objects.get(slug=post_slug)

    return HttpResponse(f"Отображение статьи с id = {article.pk}, "

                        f"Именем: {article.name}"

                        f"<br>{article.content}")

 

(site1) PS C:\python\django\site1\app> python manage.py makemigrations

(site1) PS C:\python\django\site1\app> python manage.py migrate

 

(site1) PS C:\python\django\site1\app> python manage.py createsuperuser

 

(site1) PS C:\python\django\site1\app> python manage.py shell

 

>>> from people.models import People

 

>>> People.objects.create(name='Human1', biography='Born in RU')

>>> People.objects.create(name='Human2', biography='Born in USSR')

 

Далее через админку добавьте 4 статьи, как ранее

Обратите, поле slug заполниться автоматически

 

Проверьте формирование ссылок на странице:

http://127.0.0.1:8000/ct/

 

Проверьте по аналогии со статьей:

http://127.0.0.1:8000/ct/auto-tunung/

 

Формы с GET и POST

 

Get

 

Заглушка отображения успеха app/people/templates/people/add_author.html:

<!DOCTYPE html><html lang="en">

<head><meta charset="UTF-8"><title>Успех</title></head>

<body>

<h2>Внесено</h2>

<p>{{ name }}</p><p>{{ biography }}</p>

</body>

</html>

 

Добавим в app/people/views.py:

...

def add_author(request):

    name = request.GET['name']

    biography = request.GET['biography']

    row = People(name = name, biography = biography)

    row.save()

    return render(request, 'people/add_author.html', {

        'name': name, 'biography': biography

    } )

 

В row мы заносим данные из формы и сохраняем в базу данных

 

Добавим в app/people/urls.py:

...

urlpatterns = [

    ...

    path('add_author/', add_author, name='add_author')

]

 

Дополним app/people/templates/people/ct.html:

...

<h3>GET</h3>

<form action="{% url 'add_author' %}" method="GET">

    <label>Имя</label><input name="name">

    <label>Биография</label><input name="biography">

    <button type="submit">Внести</button>

</form>

{% endblock %}

 

В form action указано имя add_author из urls.py

 

Post

 

Меняем в app/people/views.py:

...

def add_author(request):

    name = request.POST['name']

    biography = request.POST['biography']

...

 

Меняем в app/people/templates/people/ct.html:

...

<h3>POST</h3>

<form action="{% url 'add_author' %}" method="POST">

    {% csrf_token %}

    <label>Имя</label><input name="name">

    <label>Биография</label><input name="biography">

    <button type="submit">Внести</button>

</form>

{% endblock %}