В командной строке создаем приложение с названием articles:
python manage.py startapp articles
Модель
Переходим в директорию приложения и заполняем файл models.py:
from django.db import models
from django.urls import reverse
from django.utils import timezone
from core.utils import slugify, strip_tags
class PublicArticlesManager(models.Manager):
def get_queryset(self):
return super(PublicArticlesManager, self).get_queryset().filter(published__lte=timezone.now())
class Article(models.Model):
title = models.CharField('заголовок', max_length=255)
slug = models.SlugField('слаг', max_length=255, blank=True, editable=False)
text = models.TextField('текст')
summary = models.CharField('резюме', max_length=255, blank=True, editable=False)
published = models.DateTimeField('опубликована', blank=True, null=True, db_index=True)
created = models.DateTimeField('создана', auto_now_add=True)
updated = models.DateTimeField('обновлена', auto_now=True)
objects = models.Manager()
public = PublicArticlesManager()
class Meta:
verbose_name = 'статья'
verbose_name_plural = 'статьи'
ordering = ['-published']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = slugify(self.title, 255)
# очищаем стрипом текст от всех тегов для резюме
self.summary = strip_tags(self.text, 255)
super(Article, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('articles:article', args=[self.slug, self.pk])
def get_admin_url(self):
return reverse('admin:{}_{}_change'.format(self._meta.app_label, self._meta.model_name), args=[self.pk])
Слаг — это заголовок статьи, сформированный латинскими буквами. Используется для составления ссылок.
Краткая версия статьи используется для предоставления резюме в ленте последних публикаций.
Поле с датой публикации одновременно является и временем размещения статьи, и флагом. Например, если метка времени присутствует — значит статья опубликована, в противном же случае — является черновиком.
Поля с датой создания и обновления в основном носят служебный характер. Например, метка обновления в карте сайта может сообщить поисковым службам, что содержимое статьи было изменено и им необходимо заново просканировать данную публикацию.
Сортировку модели задаем по дате публикации, последние статьи — сверху.
При сохранении модели, мы формируем слаг статьи вызывая функцию slugify, которая принимает два параметра: заголовок и максимальное допустимое количество символов на выходе.
Язык заголовка на входе может быть любым, но при переводе слаг может резко увеличиться в размерах, поэтому ограничение мы выставим в соответствии с полем — 255 знаков.
Например, китайская пословица "会浮水的淹死,会骑马的摔死" состоящая всего лишь из 13 символов, при переводе обретет слаг "hui-fu-shui-de-yan-si-hui-qi-ma-de-shuai-si" что составит уже 43 символа.
Далее мы вызываем функцию strip_tags, передавая ей текст статьи. На выходе мы получим резюме публикации с ограничением в 255 знаков и без содержания каких-либо HTML тегов для эстетической составляющей.
Утилиты slugify и strip_tags мы опишем ниже.
Менеджер
PublicArticlesManager отсекает не только черновики, но и статьи, которые запланированы на определенные даты.
Например, мы можем создать запись "С новым годом!" и запланировать публикацию на первое января следующего года, и быть уверенным в том, что посетители увидят эту заметку не раньше чем наступит данный праздник.
Утилиты
В каталоге core открываем utils.py и добавляем следующее:
import unidecode
import bleach
from django.utils.text import slugify as _slugify, Truncator
def slugify(value, truncate_chars):
return Truncator(_slugify(unidecode.unidecode(value))).chars(truncate_chars, truncate='')
def strip_tags(value, truncate_chars):
return Truncator(bleach.clean(value, tags=[], strip=True)).chars(truncate_chars)
Truncator как и _slugify это стандартные утилиты Django. Первая — ограничивает текст до заданного количество символов. Вторая — заменяет знаки для правильного составления слага, например, убирает знаки препинания. Unidecode занимается непосредственно переводом.
В функции strip_tags мы используем библиотеку bleach и конкретно утилиту clean, которая удаляет все HTML-теги из переданного текста.
По умолчанию Truncator добавляет троеточие, если длина исходного текста превышает ограничение, разумеется если не указано обратное.
Администрирование
Переходим к файлу admin.py и приводим к следующему виду:
from django.contrib import admin
from django.db import models
from ckeditor_uploader.widgets import CKEditorUploadingWidget
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'slug', 'published']
list_filter = ['published']
search_fields = ['title', 'slug']
formfield_overrides = {models.TextField: {'widget': CKEditorUploadingWidget}}
В list_display перечислены поля, которые нам хотелось бы видеть при просмотре списка статей.
В list_filter указываваем поля для быстрой фильтрации.
В списке search_fields содержится список полей, по которым производится поиск.
Далее в словаре formfield_overrides мы переопределяем виджет текстового поля. Заменяя его на CKEditorUploadingWidget.
Данный виджет представляет доступ к визуальному редактору CKEditor, который позволяет сосредоточиться на написании текста самой статьи, а не отвлекаться на HTML-теги.
Дополнительно, приложение позволяет загружать изображения и прочие документы работая непосредственно в редакторе.
Указываем читабельное название приложения редактируя файл apps.py:
from django.apps import AppConfig
class ArticlesConfig(AppConfig):
name = 'articles'
verbose_name = 'Статьи'
Настройки
Для внесения корректив в директории zero открываем settings.py и добавляем приложения articles, ckeditor и ckeditor_uploader:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'ckeditor',
'ckeditor_uploader',
'core.apps.CoreConfig',
'accounts.apps.AccountsConfig',
'articles.apps.ArticlesConfig',
]
Далее мы добавляем настройки для корректной работы визуального редактора:
CKEDITOR_UPLOAD_PATH = 'uploads/'
CKEDITOR_IMAGE_BACKEND = 'pillow'
CKEDITOR_CONFIGS = {
'default': {
'skin': 'moono',
'toolbar_Full': [
['Styles', 'Format', 'Bold', 'Italic', 'Underline', 'Strike', 'SpellChecker', 'Undo', 'Redo'],
['Link', 'Unlink', 'Anchor'],
['Image', 'Flash', 'Table', 'HorizontalRule'],
['TextColor', 'BGColor'],
['Smiley', 'SpecialChar'],
['Blockquote', 'BulletedList'],
['Source'], ['RemoveFormat'], ['Maximize'],
],
'toolbar': 'Full',
'width': '100%',
'filebrowserWindowWidth': 940,
'filebrowserWindowHeight': 725,
},
}
Маршруты
Необходимо подключить маршруты для ckeditor, открываем urls.py и заменяем следующим:
from django.conf.urls import url, include
from django.contrib import admin
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
url(r'^accounts/', include('accounts.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Django Debug Toolbar
if settings.DEBUG:
import debug_toolbar
urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)),
]
Миграция
Переходим в командную строку и выполняем команды для создания и применения данной миграции:
python manage.py makemigrations
python manage.py migrate