Перейти к основному содержимому

Практикум — доска объявлений на Django

Разработчику

Зачем этот практикум

Первая программа даёт один приложение и одну модель. Справочник разбирает инструменты по отдельности. Здесь — цельный сайт: каталог объявлений с рубриками, комментариями и разграничением доступа — типичный учебный проект уровня «доска объявлений».

Перед стартом: завершите 3011, просмотрите в 301 разделы про CBV, formsets, auth и (по желанию) PostgreSQL.


Что получится

ФункцияРеализация
Главная, навигацияПриложение main, базовый шаблон
РубрикиМодель Rubric, список и фильтр объявлений
ОбъявленияМодель Listing, CRUD через CBV + формы
КомментарииСвязь FK, inline в админке, форма на странице объявления
Пользователиdjango.contrib.auth, только автор правит своё
API (опционально)DRF или JsonResponse из 301

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

Рекомендуемое разбиение на приложения (каждое — одна зона ответственности):

board_project/
manage.py
board_project/ # пакет конфигурации
settings.py
urls.py
main/ # главная, layout, статика
catalog/ # Rubric, Listing
comments/ # Comment
accounts/ # профиль, регистрация (опционально)
templates/layout/
static/

В INSTALLED_APPS подключают main, catalog, comments, accounts и стандартные contrib.*.


Модели предметной области

# catalog/models.py
from django.conf import settings
from django.db import models
from django.urls import reverse

class Rubric(models.Model):
name = models.CharField('Название', max_length=80)
slug = models.SlugField(unique=True)
order = models.PositiveSmallIntegerField(default=0)

class Meta:
ordering = ['order', 'name']
verbose_name = 'рубрика'
verbose_name_plural = 'рубрики'

def __str__(self):
return self.name


class Listing(models.Model):
rubric = models.ForeignKey(Rubric, on_delete=models.PROTECT, related_name='listings')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='listings',
)
title = models.CharField(max_length=120)
content = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ['-created_at']

def __str__(self):
return self.title

def get_absolute_url(self):
return reverse('catalog:listing_detail', kwargs={'pk': self.pk})
# comments/models.py
class Comment(models.Model):
listing = models.ForeignKey(
'catalog.Listing',
on_delete=models.CASCADE,
related_name='comments',
)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
text = models.TextField(max_length=2000)
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ['created_at']

PROTECT у рубрики не даёт удалить рубрику, пока в ней есть объявления. get_absolute_url нужен для редиректов после сохранения и ссылок в админке.


Представления и маршруты

Именованное пространство app_name = 'catalog' в catalog/urls.py:

URLViewНазначение
/TemplateView / главная mainвход
/catalog/ListingListViewвсе опубликованные
/catalog/rubric/<slug>/список с фильтром rubric__slugобъявления рубрики
/catalog/<pk>/ListingDetailViewкарточка + комментарии
/catalog/add/LoginRequiredMixin + CreateViewновое объявление
/catalog/<pk>/edit/UpdateView, проверка авторстваправка

Фильтр только опубликованных записей:

class ListingListView(ListView):
model = Listing
template_name = 'catalog/listing_list.html'
context_object_name = 'listings'
paginate_by = 10

def get_queryset(self):
qs = Listing.objects.filter(published=True).select_related('rubric', 'author')
slug = self.kwargs.get('slug')
if slug:
qs = qs.filter(rubric__slug=slug)
return qs

Правка чужого объявления закрывают переопределением get_queryset в UpdateView или mixin, сравнивающим obj.author_id с request.user.id.


Комментарии на странице объявления

В DetailView в get_context_data добавляют форму комментария. POST обрабатывают в post() того же класса или отдельным FBV:

class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']

После form.save(commit=False) выставляют author=request.user и listing_id из URL. Редирект — на get_absolute_url() объявления.


Административный сайт

@admin.register(Listing)
class ListingAdmin(admin.ModelAdmin):
list_display = ('title', 'rubric', 'price', 'published', 'created_at')
list_filter = ('published', 'rubric')
list_editable = ('published',)
search_fields = ('title', 'content')
date_hierarchy = 'created_at'
autocomplete_fields = ('author',)

Для комментариев — TabularInline на странице объявления. Подробнее — справочник, §8.


Пользователи и права

  1. createsuperuser — модерация через /admin/.
  2. Регистрация — django.contrib.auth views или форма UserCreationForm в accounts.
  3. Публикация объявления: флаг published=False по умолчанию; модератор включает в админке или автор с правом catalog.can_publish_listing (кастомное Permission в Meta модели).

Цепочка входа и сброса пароля — готовые CBV в 301, §14.


Шаблоны и статика

Базовый шаблон templates/layout/base.html с блоками title, content, навигацией по рубрикам (Rubric.objects.all() в context processor или в каждом view). Статика — {% load static %}, в продакшене — collectstatic.

Оформление через Bootstrap (django-bootstrap5 и аналоги) опционально: подключают в INSTALLED_APPS и наследуют их теги в layout.


База данных

Для разработки достаточно SQLite. Для полнотекстового поиска и сложных ограничений — PostgreSQL (см. 301, PostgreSQL). В settings.py:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('PGDATABASE', 'board'),
'USER': os.environ.get('PGUSER', 'board'),
'PASSWORD': os.environ.get('PGPASSWORD', ''),
'HOST': os.environ.get('PGHOST', 'localhost'),
'PORT': os.environ.get('PGPORT', '5432'),
}
}

REST для того же проекта

После HTML-версии те же модели подключают к DRF: ListingSerializer, ListingViewSet, router на /api/listings/. Отдельный фронтенд (React, Angular) ходит на API; сессионная auth или JWT — в 3012.

Минимальный вариант без DRF описан в 3012.


Порядок внедрения (чек-лист)

  1. Проект и приложения, migrate, суперпользователь.
  2. Модели Rubric, Listing, миграции, фикстуры рубрик (fixtures или loaddata).
  3. Админка, несколько тестовых объявлений.
  4. ListView / DetailView, шаблоны, пагинация.
  5. CreateView / UpdateView с LoginRequiredMixin.
  6. Комментарии и форма на детальной странице.
  7. Фильтр по рубрике, главная с навигацией.
  8. Тесты: список 200, создание только для авторизованных.
  9. (Опционально) DRF или деплой — 30.md, 301 §16.

Дальше

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").