Evgenii Legotckoi
Evgenii Legotckoi18 вересня 2016 р. 03:41

Django - Підручник 003. Модель, шаблон, перегляд на Django

У Django використовується модульна система програми, коли одна програма складається з декількох програм, які відповідають кожна за свій функціонал. Як Ви встигли помітити, на момент написання статті, на сайті є розділ "База Знань", в якому присутні кілька розділів, за якими вже розділені статті.

При роботі з попереднім сайтом на Wordpress, безглуздо витрачався час на додавання нових статей до списку уроків з Qt. На цьому ж сайті сторінка автоматично генерується і при збереженні нової статті зі статусом опубліковано, дана стаття автоматично потрапляє до списку відповідного розділу.

Пропоную розібратися, як це реалізується у мінімальному варіанті на прикладі EVILEG COM.


Структура проекта

Саме в даному веб-сайті вживаються два додатки в проекті:

  1. home - програма, яка відповідає за основну сторінку index, сторінки помилок та за базові шаблони
  2. knowledge - додаток, який відповідає саме за статті та розділи статей

Якщо подивитись детально, то структура проекту буде такою:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    home/
        __init__.py
        admin.py
        apps.py
        models.py
        tests.py
        urls.py
        views.py
        templates/
            home/
                base.html
    knowledge/
        __init__.py
        admin.py
        apps.py
        models.py
        tests.py
        urls.py
        views.py
        templates/
            knowledge/
                article.html
                index.html
                section.html

Пара слів про створення проекту та додатків у проекті. Використовувалося середовище розробки Pycharm, яке готує проект із включеними основними модулями, у тому числі і адмінкою, тому розповідатиму про те, що не було включено за замовчуванням.

Для створення програм необхідно скористатися такими командами:

python manage.py startapp home
python manage.py startapp knowledge

Налаштування settings.py

Для того, щоб були доступні модулі програм, необхідно підключити їх до конфігураційного файлу проекту.

INSTALLED_APPS = [
    'home.apps.HomeConfig',
    'accounts.apps.AccountsConfig',
    ...
]

Моделі

У проекті використовується два види моделей:

  1. Section - розділи, які поєднують всі статті за однією загальною ознакою, належністю до певної тематики
  2. Article - самі статті

Моделі описуються у файлі models.py

Для розробки цих моделей даних було застосовано успадкування від вбудованого в Django класу Model. А також використовувалася модель користувача User, оскільки у статтях вказується автор із числа зареєстрованих на сайті користувачів.

from django.db import models
from django.contrib.auth.models import User

Розділ

Модель розділу складається з:

  1. Найменування - title розділу
  2. URL - використовується для формування частини адреси на сайті, тобто зберігаються не абсолютні шляхи, а частина шляху до розділу
  3. Опис – яке виводиться на початку сторінки перед списком статей

Подивимося на реалізацію моделі у коді:

class Section(models.Model):
    class Meta:
        db_table = "section"

    section_title = models.CharField(max_length=200)
    section_url = models.CharField(max_length=50)
    section_description = TextField()

    def __str__(self):
        return self.section_title

Клас Meta дозволяє перевизначити деякі параметри моделі, які безпосередньо стосуються таблиць у базі даних. У разі визначається, як називатиметься таблиця.

У моделі є три поля, два з них мають тип CharField, який має на увазі, невеликі за розміром записи з обмеженням числа символів, що вводяться.

Тип TextField, має на увазі, що вводитиметься досить великий довільний масив символів, максимальний розмір якого заздалегідь невідомий.

Перевизначення методу str , а це саме перевизначення, а не новий метод, дозволяє повернути назву статті, яка відображатиметься в адмінці. Справа в тому, що якщо не перевизначити цей метод, всі записи в адміці будуть виглядати, як Section Object, і розібратися де і що буде дуже проблематично при великій кількості розділів.

Стаття

Модель складається з наступних полів:

  1. Найменування статті
  2. Розділ - це зовнішній ключ на таблицю Розділів, що визначає приналежності статті до певного розділу
  3. Автор – це зовнішній ключ на таблицю користувачів, з яких вибиратиме автор статті
  4. Дата - дата та час публікації
  5. Контент - текстове поле аналогічне опису, як у моделі розділів
  6. Статус – припускаю кілька статусів для статей, на даний момент використовується лише два:
  7. Чернетка - значення 0
  8. Опубліковано - значення 1
class Article(models.Model):
    class Meta:
        db_table = "article"

    article_title = models.CharField(max_length=200)
    article_section = models.ForeignKey(Section)
    article_author = models.ForeignKey(User)
    article_date = models.DateTimeField('Дата публикации')
    article_content = TextField()
    article_status = models.IntegerField()

    def __str__(self):
        return self.article_title

Реєстрація моделей в адмінці

Для того, щоб мати можливість редагувати статті та розділи з адмінки, необхідно зареєструвати їх у модулі адмінки. Робиться це у файлі admin.py відповідної програми.

from django.contrib import admin

from .models import Section, Article

admin.site.register(Section)
admin.site.register(Article)

Міграція

Написали моделі, впевнені, що моделі мають всі потрібні поля? Тоді потрібно зробити міграцію баз даних. Для цього виконуємо наступні команди.

python manage.py makemigrations
python manage.py migrate

Примітка

Primary Key у загальному випадку підставляється автоматично і є автоінкрементованим, тому ми його не вказуємо.

Шаблони

Шаблони Django просто чудова річ, особливо якщо враховувати те, що вони можуть успадковуватися і певні блоки будуть перевизначатися.

У проекті створено один базовий шаблон base.html у додатку home , від якого успадковуються всі інші сторінки. Умовна структура шаблону:

{% block head %}
{% endblock %}
{% block content %}
    {% block page %}
    {% endblock page %}
    {% block sidebar %}
    {% endblock %}
{% endblock content %}
{% block footer %}
{% endblock %}

У цьому випадку є п'ять блоків, які можна буде перевизначити при успадкування шаблонів на інших сторінках.

index.html

Це шаблон основної сторінки програми, де відображається список усіх розділів. За допомогою оператора extends , ми вказуємо, що успадковуємося від базового шаблону програми home. А продублювавши блок page, з базового шаблону та прописавши в нього вміст, ми перевизначаємо даний блок базового шаблону вносячи потрібну нам інформацію для конкретної сторінки.

Як Ви могли помітити, у мові шаблонів Django використовуються конструкції if, for, url. Але звідки взялися змінні section_list та section? - section_list передається як контекстного значення шаблон під час підготовки даних у поданні ( view ). Перевіривши, що список розділів існує, ми проходимо всі елементи списку підставляючи необхідні значення шаблону. Оскільки section відповідає моделі Section, відповідні назви полів моделі підхоплюються автоматично.

Що стосується url , то даний оператор вказує, що необхідно взяти шаблон адреси з програми knowledge з ім'ям section, а також ми знаємо, що цей шаблон адреси має одну змінну, якій ми присвоюємо url об'єкта section. Дані шаблони описуються у файлі urls.py. Ми їх розглянемо трохи згодом.

{% extends 'home/base.html' %}
{% block page %}
    <article>
    <h1>Разделы</h1>
    {% if section_list %}
        <ul>
        {% for section in section_list %}
            <li>
            <a href="{% url 'knowledge:section' section.section_url %}">{{ section.section_title }}</a>
            </li>
        {% endfor %}
        </ul>
    {% endif %}
    </article>
{% endblock %}

section.html

У шаблоні розділів відбувається формування списку статей, які відповідають цьому розділу. В якості контексту шаблон передається змінна section, яка містить інформацію про об'єкт розділу. Ми підставляємо назву розділу та його опис. Зауважте, що опис є спеціальним аргументом safe, який вказує на збереження форматування. Це необхідно зробити, щоб зберегти html розмітку, інакше користувач побачить замість красиво оформленого опису код html верстки.

Також тут наведено варіант із забором інформації про всі статті, які відносяться до даного розділу, а зроблено це за допомогою виклику section.article_set.all, тобто ми забираємо всі статті, які мають зовнішній ключ на даний розділ. Ну і сортуємо статті за допомогою dictsort із зазначенням параметра, за яким відбувається сортування.

А в блоці if перевіряється статус статті, тобто ті статті, які не опубліковані, не відображатимуться.

Що ж до url, то знову використовується шаблон адреси, але з двома аргументами.

{% extends 'home/base.html' %}
{% block page %}
    <article>
    <h1>{{ section.section_title }}</h1>
        <p>{{ section.section_description|safe }}</p>
        <h2>Статьи</h2>
        <ul>
        {% for article in section.article_set.all|dictsort:'article_title' %}
            {% if article.article_status %}
            <li>
            <a href="{% url 'knowledge:article' section.section_url article.id %}">
                {{ article.article_title }}
            </a>
            </li>
            {% endif %}
        {% endfor %}
        </ul>
    </article>
{% endblock %} 

article.html

Як бачите, шаблон для статті досить мізерний, щоб щось додавати про нього.

{% extends 'home/base.html' %}
{% block page %}
    <article>
        <h1>{{ article.article_title }}</h1>
        <p>{{ article.article_content|safe }}</p>
    </article>
{% endblock %}

Примітка

Якщо Ви подивитеся на структуру проекту, перш ніж побачити, що програми містять у папках templates ще папки, які мають таке ж найменування як і програми, а вже в них знаходяться шаблони сторінок. Робиться це для того, щоб точно визначати, який шаблон використовуватиметься. Django шукає шаблони по всіх папках та вибирає перший шаблон, який відповідає найменуванню. І якщо назви перетинаються, наприклад index.html, може бути обраний неправильний шаблон. Тому використовується простір назв, що точно визначити потрібний шаблон.

Подання

В даному випадку робота з представлення досить обмежена, тому наведу одразу повний лістинг файлу views.py.

Ми успадковуємося від класу View та перевизначаємо метод get, який відповідає за виконання GET запиту, тобто запиту сторінки користувачем. Також імпортуємо методи render_to_response, який оброблятиме шаблон у потрібному нам контексті, та get_object_or_404 , який автоматично поверне помилку 404, якщо об'єкт не буде знайдений, за відповідною змінною .

У EKnowledgeIndex забираються абсолютно всі розділи з сортуванням за назвою та поміщаються в контекст під ім'ям section_list, Ви пам'ятаєте, що ця змінна використовувалася у шаблоні index.html.

У ESectionView забирається вже конкретний Розділ, за аргументом, який передається в url, що запитується. kwargs відповідає за змінні, які вичленюються з url за шаблоном, заданим у файлі urls.py.

У EArticleView все набагато цікавіше. Справа в тому, що в шаблоні url використовується дві змінні, але для отримання статті достатньо зробити запит на id, тобто Primary Key , який передається в url.

from django.views import View
from django.shortcuts import render_to_response, get_object_or_404

from .models import *


class EKnowledgeIndex(View):
    template_name = 'knowledge/index.html'

    def get(self, request, *args, **kwargs):
        context = {}
        context['section_list'] = Section.objects.all().order_by('section_title')

        return render_to_response(template_name=self.template_name, context=context)


class ESectionView(View):
    template_name = 'knowledge/section.html'

    def get(self, request, *args, **kwargs):
        context = {}
        section = get_object_or_404(Section, section_url=self.kwargs['section'])

        context['section'] = section

        return render_to_response(template_name=self.template_name, context=context)


class EArticleView(View):
    template_name = 'knowledge/article.html'

    def get(self, request, *args, **kwargs):
        context = {}
        article = get_object_or_404(Article, id=self.kwargs['article_id'])

        context['article'] = article

        return render_to_response(template_name=self.template_name, context=context)

URL шаблони

Добре, все, що потрібно написано, але залишилося зовсім небагато, щоб все запрацювало, а саме налаштувати шаблони url, за якими будуть оброблятися запити.

Особливістю Django є те, що дані шаблони є перевірками за регулярними виразами, а не виводяться з контролерів моделей. Особисто для мене цей підхід виявився зрозумілим та прозорим, і він мені подобається. Тож давайте подивимося, як оживити наші сторінки.

mysite/mysite/urls.py

Для початку необхідно задати шаблони у файлі mysite/mysite/urls.py , який визначатиме, до якої програми надіслати запит. Тут Ви бачите, що є направлення в додаток home, там відображається головна сторінка сайту, але я не заглиблюватимусь в опис даної частини, оскільки про неї ми не говоримо в даній статті. Також є направлення в адмінку сайту та додаток knowledge, яке відповідає якраз за розділи та статті.

Через include, підключаються файли urls.py у додатках home та knowledge.

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^', include('home.urls')),
    url(r'^knowledge/', include('knowledge.urls')),
    url(r'^admin/', admin.site.urls),
]

mysite/knowledge/urls.py

app_name відповідає за простір імен у шаблонах. Пам'ятаєте? Трохи вище у шаблоні був запис knowledge:section.

Ну а регулярочки визначають, що і куди прямуватиме. Що стосується записів

та , це якраз ті змінний, які оброблялися в методі get в уявленнях і передавалися за допомогою kwargs.

Ну а name вказує найменування URL шаблону, тобто друга частина у 'knowledge:section'.

Ну і останнє, що хотілося б відзначити, так це символ ^ , який є у шаблоні. За допомогою нього відкидається розпізнана частина шаблону. Тобто в цих url також присутній на початку knowledge/ , яка була розпізнана раніше в mysite/mysite/urls.py.

from django.conf.urls import url

from . import views

app_name = 'knowledge'
urlpatterns = [
    url(r'^$', views.EKnowledgeIndex.as_view(), name='index'),
    url(r'^(?P<section>[\w]+)/$', views.ESectionView.as_view(), name='section'),
    url(r'^(?P<section>[\w]+)/(?P<article_id>[0-9]+)/$', views.EArticleView.as_view(), name='article')
]

Для Django рекомендую VDS-сервер хостера Timeweb .

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

ПК
  • 03 березня 2018 р. 06:26

Спасибо за ваши статьи. Очень понятно и качественно пишите. Есть пара моментов в  данной статье, которые мне кажутся спорными. Например, именование полей внутри моделей(article_title, article_date вместо просто title и date). Думаю добавлять в качестве префикса имя модели несколько избыточно - поля без модели не используются и без всяких префиксов всегда понятно откуда они. Второй момент это использование в шаблоне для ссылки на конкретный объект тег url. Гораздо удобнее реализовать для модели метод get_absolute_url и использовать его. Это даст возможность полностью менять схему урлов без поиска и переписывания всех шаблонов. К тому же этот метод используется стандартной админкой для формирования ссылок "посмотреть на сайте". Ну и ваши представления легче было наследовать от соответсвующих generic views - DetailView, ListView.
Хотя может это всё допущения чтобы не усложнять материал.

Evgenii Legotckoi
  • 04 березня 2018 р. 15:14

Спасибо за отзыв.

По факту согласен со всеми вашими примечаниями.
Но это действительно допущения, чтобы не перегружать материал всем подряд. А так да, активно пользуюсь get_absolute_url и дженериками . В одной статье есть и про дженереки информация.
Владислав Меленчук
  • 27 квітня 2020 р. 09:35

Хотелось спросить, можно ли создать многоуровневые url из свойств модели? Я вот с часик лежал, искал как это сделать не нашел, вопрос на хабре оставил, никто не ответил. Оригинальная документация говорит только об одном слаге, а все примеры заканчиваются на том, что есть домен и к нему slug. Я просто хотел прикрепить запись к определенной категории, и еще одной субкатегории, т.е типо домен/категория/подкатегории/запись
я сделал подобное, но блин, доступ к записи осуществляется даже с др.категории и вообще с любимым текстом, т.е ссылка каноническая, могу перейти и по test/test/запись и по nowork/test/запись, передавал в модель свойства вот так: kwargs=("url":self.url, "cat":self.cat.url, "sub":self.sub.url)

Если я правильно понимаю ваш вопрос, то нечто подобное у меня реализовано для страниц правил пользования сайтом, вот например страница тем форума

Но там я формирую полный путь, укладываю его в slug, а вот url шаблон у меня сделан так

path('<path:slug>/', PageView.as_view(), name='page')

Но у меня реализация там сделана за счёт того, что я задаю частичный url, который собирает все parent страницы формирует уже итоговый url и кладёт его куда надо.

Ещё делал раньше как вы делаете, но я так полагаю, что у вас код смотреть нужно, наверняка у вас неправильно написан код где-то:

  • или метод get_absolute_url
  • или url диспетчера.
  • или не выкидываете ошибку 404 по View, если из сформированного url не удаётся найти объект.

К слову говоря, если делали по этой статье, то там тоже в EArticleView нужно ещё добавить проверку на slug раздела, и если не совпадаетЮ, то выкинуть ошибку 404. Так что да, косяк в статье.

Владислав Меленчук
  • 27 квітня 2020 р. 09:53
  • (відредаговано)

Я делаю вот так у себя в модели для получения ссылки:
Там где доработать это я закомментил, ибо получалось доступность по всем категориям, потом выяснил, что так вообще по всей ссылки.

models.py

# Формируем URL
    def get_absolute_url(self):
        #games = self.games.all()[0] доработь 'games': games.url, ->
        return reverse('article_detail', kwargs={'category': self.category.url, 'slug': self.url})

Вьюшка:

# Полная статья
class ArticleDetail(DetailView):
    model = Article
    slug_field = "url"

URLs

path('articles/<category>/<slug>/', views.ArticleDetail.as_view(), name="article_detail"),

Запись доступна по articles/blog/название статьи, но таким же образом она доступна к неподвязанной категории, т.е articles/files/название статьи
Также можно любой текст можно подставить, articles/131411414/название статьи и будет тоже доступно.

Ну я так и думал. Здесь нужно или обычный View использовать, или формировать slug так, чтобы он содержал slug категории, и как у меня использовать path, но тут тогда будет проблем в том, что тогда url категории скорее всего перестанет работать.

Так что или берите обычный View, добавляйте туда метод get, и проверяйте как slug статьи, так и slug категории, которая будет привязана к статье, либо переопределяйте метод get_object у DetailView, но там также нужно будет ковырять как slug статьи, так и slug категории

Хорошо, посмотрю что получится с простым View

МЗ
  • 08 січня 2021 р. 15:05

Отлично. Спасибо. Вопрос, как мне теперь передать на индексную страницу(ту которая по умолчанию) содержание только статей, без секций? И вообще передать на дефолтную страницу? Что то не получается. PS: в джанго - недавно, неделю.

МЗ
  • 08 січня 2021 р. 15:18

Я дура. Разобралась. Но, остаюсь вашим читателем)

МЗ
  • 09 січня 2021 р. 14:30
from django.shortcuts import *
from django.http import *
from django.http import HttpResponseRedirect
from django.contrib.auth.forms import *
from django.urls import reverse_lazy
from django.views import generic
from django.views.generic import *
from .models import Post

# Create your views here.
def index(request):
    postview = Post.<- а object отсуствует?
    return render(request,'index.html')
def about(request):
    return render(request,'about.html')
def contact(request):
    return render(request,'contact.html')
def home (request):
    return render(request, 'home/index.html')
class SignUpView(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'signup.html'


МЗ
  • 09 січня 2021 р. 14:31

отсутствует object

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • 26 квітня 2024 р. 16:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
d
  • dsfs
  • 26 квітня 2024 р. 16:45

C++ - Тест 002. Константы

  • Результат:50бали,
  • Рейтинг балів-4
d
  • dsfs
  • 26 квітня 2024 р. 16:35

C++ - Тест 001. Первая программа и типы данных

  • Результат:73бали,
  • Рейтинг балів1
Останні коментарі
k
kmssr09 лютого 2024 р. 07:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 14:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 23:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 21:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 10:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
Gar22 квітня 2024 р. 17:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 квітня 2024 р. 19:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 квітня 2024 р. 18:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 квітня 2024 р. 14:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex04 квітня 2024 р. 16:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Слідкуйте за нами в соціальних мережах