← Часть 2 ORM и основы работы с базами данных
Обзор проекта
Приложение состоит из 3 основных разделов – главной страницы, резюме и секции «Контакты». На главной странице выводятся все работы, перечень услуг (со списками используемых инструментов), и отзывы заказчиков:
Главная страница
Раздел «Обо мне», по сути, является резюме владельца портфолио – здесь можно разместить подробные сведения об образовании, опыте работе и уровне профессиональных навыков:
Резюме разработчика
В разделе «Контакты» перечислены всевозможные способы связи с владельцем портфолио, но самое главное – там еще есть форма для отправки сообщения:
Контактная форма
Подробная информация о реализации проекта выводится на отдельной странице. Здесь можно написать о ходе разработки, особенностях проекта и его технологическом стеке:
А теперь расскажем, как это все сделать.
? Библиотека питониста Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста» ?? Библиотека собеса по Python Подтянуть свои знания по Python вы можете на нашем телеграм-канале«Библиотека собеса по Python» ?? Библиотека задач по Python» Интересные задачи по Python для практики можно найти на нашем телеграм-канале«Библиотека задач по Python»
Ход работы
Весь код есть в репозитории: просто скопируйте, если что-то не получается.
Установка Django, Pillow и настройки проекта
Для хранения ссылок на изображения (и автоматической загрузки изображений в папку media ) нужна библиотека Pillow – установите ее сразу после Django:
python -m venv myportfoliovenv cd myportfolio venvscriptsactivate pip install django pip install pillow django-admin startproject config . python manage.py startapp portfolio
Добавьте приложение portfolio в INSTALLED_APPS в config/settings.py :
INSTALLED_APPS = [ 'portfolio.apps.PortfolioConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
Также добавьте в config/settings.py настройки для вывода сообщений:
MESSAGE_TAGS = { messages.DEBUG: 'alert-secondary', messages.INFO: 'alert-info', messages.SUCCESS: 'alert-success', messages.WARNING: 'alert-warning', messages.ERROR: 'alert-danger', }
И пути к папкам static и media :
STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / "static"] MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / "media"
Для удобства еще можно изменить язык админки на русский, а время – на московское:
LANGUAGE_CODE = 'ru' TIME_ZONE = 'Europe/Moscow'
Чтобы Django мог работать с папками static и media , нужно добавить эти пути в файл config/urls.py :
from django.contrib import admin from django.urls import path from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Создание базы данных и аккаунта админа
Инициализируйте базу и создайте аккаунт суперпользователя:
python manage.py migrate python manage.py createsuperuser
Теперь можно приступить к созданию нужных таблиц в БД. Сохраните эти модели в файле portfolio/models.py :
from django.db import models class Skill(models.Model): name = models.CharField(max_length=30) level = models.CharField(max_length=3) class Meta: ordering = ['id'] verbose_name = 'Навык' verbose_name_plural = 'Навыки' def __str__(self): return self.name class Category(models.Model): engname = models.CharField(max_length=25) rusname = models.CharField(max_length=25) class Meta: ordering = ['id'] verbose_name = 'Категория' verbose_name_plural = 'Категории' def __str__(self): return self.rusname class Work(models.Model): title = models.CharField(max_length=150) slug = models.SlugField(max_length=150, unique=True) category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='works') image = models.ImageField(upload_to='works') description = models.TextField() stack = models.TextField() link = models.URLField(max_length=200) class Meta: ordering = ['-id'] verbose_name = 'Работа' verbose_name_plural = 'Работы' def __str__(self): return self.title class Service(models.Model): name = models.CharField(max_length=25) icon = models.CharField(max_length=50) description = models.CharField(max_length=200) class Meta: ordering = ['id'] verbose_name = 'Сервис' verbose_name_plural = 'Виды сервиса' def __str__(self): return self.name class Item(models.Model): name = models.CharField(max_length=150) service = models.ForeignKey(Service, on_delete=models.CASCADE) class Meta: ordering = ['-id'] verbose_name = 'Инструмент' verbose_name_plural = 'Инструменты' def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=15) lastname = models.CharField(max_length=15) about = models.TextField() skills = models.ManyToManyField(Skill, related_name='author') image = models.ImageField(upload_to='author') class Meta: ordering = ['-id'] verbose_name = 'Автор' verbose_name_plural = 'Авторы' def __str__(self): return f'{self.name} {self.lastname}' class Message(models.Model): name = models.CharField(max_length=100) email = models.EmailField() subject = models.CharField(max_length=100) message = models.TextField() created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at'] verbose_name = 'Сообщение' verbose_name_plural = 'Сообщения' def __str__(self): return f'Сообщение от {self.name}: {self.subject}' class Testimony(models.Model): name = models.CharField(max_length=15) lastname = models.CharField(max_length=15) image = models.ImageField(upload_to='clients') text = models.TextField() class Meta: ordering = ['-id'] verbose_name = 'Заказчик' verbose_name_plural = 'Заказчики' def __str__(self): return f'{self.name} {self.lastname}'
А затем подготовьте и примените миграции (изменения в структуре БД):
python manage.py makemigrations python manage.py migrate
Чтобы с БД можно было работать в админке, модели нужно зарегистрировать в файле portfolio/admin.py :
from django.contrib import admin from .models import Skill, Author, Category, Testimony, Work, Item, Service, Message class ItemInline(admin.TabularInline): model = Item class ServiceAdmin(admin.ModelAdmin): inlines = [ItemInline] admin.site.register(Category) admin.site.register(Testimony) admin.site.register(Skill) admin.site.register(Service, ServiceAdmin) admin.site.register(Author) admin.site.register(Work) admin.site.register(Item) admin.site.register(Message)
Разберемся, что и как определяют эти модели.
Skill – навыки. Название и уровень навыка позволяют рендерить на фронтенде эти данные:
Визуализацией навыков занимается Bootstrap
Данные из Category используются не только для указания категории, к которой принадлежит проект, но и для фильтрации работ с помощью плагина Isotope.js :
<div id="filters" class="filters"> <a href="#" data-filter="*" class="active">Все</a> {% for category in categories %} <a href="#" data-filter=".{{ category.engname }}">{{ category.rusname }}</a> {% endfor %} </div>
Work , как и модели Author и Testimony , использует ImageField для автоматической загрузки изображений в соответствующие поддиректории media , а также для создания и хранения ссылок на эти изображения. Работу ImageField обеспечивает библиотека Pillow :
image = models.ImageField(upload_to='works')
Для хранения ссылок на готовые сайты используется поле URLField .
В модели Service хранятся виды услуг. Поскольку в оформлении портфолио используются иконки BoxIcons, в поле icon нужно сохранять название нужной иконки в этом наборе, например, bx bx-laptop . При использовании другого набора, скажем, Font Awesome , нужно вводить названия иконок в соответствующем этому набору формате.
Item – вид инструмента, относящегося к конкретному виду услуг. С помощью данных из Item стек инструментов можно рендерить в виде списка с иконками:
{% for item in service.item_set.all %} <li><span class='bx bx-chevron-right'></span>{{ item.name }}</li> {% endfor %}
Другой способ отрендерить список в нужном стиле – сохранить список с HTML-тегами и Bootstrap стилями в базе, и вывести его в шаблоне с помощью тега |safe
:
<h4 class="h4 mb-3">Технологический стек</h4> {{ work.stack|safe }}
Так нужно сохранить список в базе
Message сохраняет и показывает (в админке) все полученные сообщения. Чтобы получать сообщения на почту, нужно подключить собственный (сложнее) или сторонний (гораздо проще) SMTP-сервер. Здесь показано, как получать сообщения с помощью SMTP Яндекса.
Отзывы заказчиков сохраняются в таблице Testimony , а данные о владельце портфолио – в Author .
Статья по теме ?? Создаем рекрутинговый портал на Django: часть 1
Представления и маршруты
Все представления в этом проекте – функциональные . Написание представлений на основе классов мы рассмотрим в одной из последующих статей. Эти представления нужно сохранить в файле portfolio/views.py :
from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from .models import Author, Category, Work, Service, Testimony, Item, Message def index(request): categories = Category.objects.all() works = Work.objects.all() services = Service.objects.all() testimonies = Testimony.objects.all() context = { 'categories': categories, 'works': works, 'services': services, 'testimonies': testimonies, } return render(request, 'index.html', context) def about(request): author = Author.objects.get() return render(request, 'about.html', {'author': author}) def work_detail(request, slug): work = get_object_or_404(Work, slug=slug) testimonies = Testimony.objects.all() context = { 'work': work, 'testimonies': testimonies, } return render(request, 'work_detail.html', context) def contact(request): if request.method == 'POST': msg = Message( name=request.POST['name'], email=request.POST['email'], subject=request.POST['subject'], message=request.POST['message'] ) msg.save() messages.success(request, 'Сообщение отправлено!') return redirect('contact') return render(request, 'contact.html')
Представление index передает в шаблон index.html все записи, сохраненные в таблицах Work , Category , Service и Testimony , потому что на главной странице выводятся все данные, без сокращений. Если портфолио объемное, имеет смысл ограничить количество работ с помощью среза works = Work.objects.all()[:3]
, а все работы вывести на отдельной странице с пагинацией. В следующем проекте, посвященном разработке блога, мы разберем процесс создания пагинации.
В таблице Author хранится всего одна запись, поэтому для ее извлечения используется запрос author = Author.objects.get()
. В случае создания некой платформы для размещения портфолио, где авторов множество, в эту функцию нужно передавать id
или username
конкретного владельца.
Представление work_detail выводит информацию о каждом проекте в отдельности, причем для извлечения данных используется не id
работы, а слаг :
work = get_object_or_404(Work, slug=slug)
Благодаря использованию слага, ссылка выглядит как http://site.com/work/design-studio/, а при использовании id
она бы выглядела как http://site.com/work/5/.
Представление contact обеспечивает обработку данных из контактной формы. В проекте используется обычная HTML-форма, которая самостоятельно отслеживает заполнение полей при помощи тега required
. В Django есть отличный модуль для работы с формами, который предоставляет всю возможную функциональность для валидации данных (однако оформление формы разработчику все равно придется делать самостоятельно). В одном из последующих проектов мы разберем использование Django-форм и все способы придания им привлекательного внешнего вида.
Чтобы Django мог обработать конкретную форму (в случае, если их на странице несколько), нужно добавить ссылку на нужное представление в form action
:
<form action="{% url 'contact' %}"
Кроме того, Django обязательно нужен токен внутри формы:
{% csrf_token %}
Все представления приводятся в действие маршрутами . Сохраните эти маршруты в portfolio/urls.py :
from django.urls import path from .views import index, contact, about, work_detail urlpatterns = [ path('', index, name='index'), path('about/', about, name='about'), path('work/<slug:slug>/', work_detail, name='work_detail'), path('contact/', contact, name='contact'),
И не забудьте включить маршруты portfolio в config/urls.py :
urlpatterns = [ path('admin/', admin.site.urls), path('', include('portfolio.urls')),
Шаблоны
Для вывода данных на фронтенде портфолио использует несколько шаблонов:
base.html – основной шаблон проекта. Здесь определены навигация и футер (поскольку они выглядят одинаково на всех страницах сайта), а также подключены все нужные скрипты на JavaScript, HTML/CSS-стили Bootstrap, шрифт Google, favicon и т. д.
index.html – как и все последующие шаблоны, наследует все стили base.html с помощью тега {% extends 'base.html' %}
. Выводит данные обо всех проектах, услугах, категориях и отзывах.
work_detail.html – показывает подробности реализации каждого проекта.
about.html – резюме владельца.
contact.html – все контакты разработчика и форма для отправки сообщений.
Подведем итоги
При желании готовое портфолио можно экспортировать в статические HTML/CSS/JS-файлы с помощью django-distill, а для обработки контактной формы подключить сервис вроде Formspree. В таком виде портфолио можно будет разместить на GitHub Pages . В одном из последующих проектов мы рассмотрим процесс преобразования динамического Django-сайта в статический.
***
Содержание курса
Часть 1: Django — что это? Обзор и установка фреймворка, структура проекта
Проект 1: Веб-приложение на основе XLSX вместо базы данных
Часть 2: ORM и основы работы с базами данных
Проект 2: Портфолио разработчика