Евгений Легоцкой18 сентября 2016 г. 3: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

Section

Модель раздела состоит из:

  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, и разобраться где и что находится будет весьма проблематично при большом количестве разделов.

Article

Модель состоит из следующих полей:

  1. Наименование статьи
  2. Раздел - это внешний ключ на таблицу Разделов, определяет принадлежности статьи к определённому разделу
  3. Автор - это внешний ключ на таблицу пользователей, из которых будет выбирать автор статьи
  4. Дата - дата и время публикации
  5. Контент - текстовое поле аналогично описанию, как в модели разделов
  6. Статус - предполагаю несколько статусов для статей, на данный момент используется лишь два:
    1. Черновик - значение 0
    2. Опубликовано - значение 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'.

Ну а регулярочки определяют, что и куда будет направляться. Что касается записей <section> и <article_id>, то это как раз те переменный, которые обрабатывались в методе 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 хостинг.
Поддержать автора Donate
ПК

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

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

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

Хотелось спросить, можно ли создать многоуровневые 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. Так что да, косяк в статье.

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

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

МЗ

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

МЗ

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

МЗ
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'


МЗ

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
НИ

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

  • Результат:90баллов,
  • Очки рейтинга8
НИ

C++ - Тест 003. Условия и циклы

  • Результат:92баллов,
  • Очки рейтинга8
НИ

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

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
D

Django - Урок 001. Развёртывание сайта на Django + PostgreSQL + Gunicorn + Nginx

А почему нельзя? Где можно об этом почитать? Киньте, пожалуйста, в меня ссылкой.
D

Django - Урок 049. Оптимизация производительности Django на примере боевого проекта

Огромное спасибо вам за статью! Для меня стали открытием select_related и prefetch_related
t
  • t1m4
  • 24 февраля 2021 г. 2:56

Django - Урок 052. Переопределение модели пользователя

В данном случае я заходил под superuser но все равно не появлялись эти поля

Django - Урок 001. Развёртывание сайта на Django + PostgreSQL + Gunicorn + Nginx

Поднял сервис с помощью systemd, вот по этому мануалу: https://habr.com/ru/post/501414/
t
  • t1m4
  • 23 февраля 2021 г. 7:11

Django - Урок 052. Переопределение модели пользователя

А как дать ему эти права?
Сейчас обсуждают на форуме

Как совместить таблицу и дерево в Qt

Добрый день. Имеется таблица QTableView, нужно у некоторых строк сделать возможность раскрытий, как в QTreeVidget и отоборажать в них аналогичные строки. Скажите пожалуйста, как это мо…
J

не получаеться добавить списки в списокб ошибка в петле

Hello dear, Flexible discounts for every order == >>> Sign up for free !!! <<< As with any skill, the key to learning how…
KM
  • BlinCT
  • 5 марта 2021 г. 6:21

Работа с QJsonObject и получение данных из него

Всем привет. Работаю с данными в json, создаю обьект и заполняю его. После мне надо проверить его. Вот таким выписом я получаю данные qDebug() << "root: " << root.k…

Не отдаётся статика на виртуальной машине

В итоге выставил 775 на все файлы проекта и всё заработало. Но я не уверен, что так правильно.
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB