🐍🚀 Создаем рекрутинговый портал на Django: часть 1
Разрабатываем портал, на котором каждый рекрутер сможет найти своего разработчика :). Система поиска позволяет подобрать нужного специалиста по резюме, портфолио или описанию проектов; рейтинг проекта поможет оценить квалификацию, а мессенджер – послать оффер.
Это достаточно сложный проект, состоящий из двух взаимосвязанных приложений – Projects и Users. Разработчики могут создавать профили и добавлять в портфолио проекты. Предусмотрены поиск, оценка проектов и обмен сообщениями. Реализована фильтрация по тегам (для проектов) и по скиллам (для профилей). Весь код проекта ITfinder находится здесь.
Обзор проекта
Сайт включает:
- Систему аутентификации и авторизации.
- Поиск по профилям, навыкам, тегам, описаниям проектов.
- Фильтрацию по тегам и скиллам.
- CRUD операции для создания, редактирования, удаления профилей, проектов, тегов и скиллов.
- Мессенджер.
- Систему оценок и отзывов о проектах.
Первый этап
Работа над Django проектом начинается с установки зависимостей, создания базовой структуры проекта и входящих в него приложений.
Установка Django и создание структуры проекта
Выполним установку фреймворка и зависимостей с помощью встроенного менеджера виртуального окружения venv:
python -m venv itfinder\venv cd itfinder venv\scripts\activate pip install -r requirements.txt
Создадим новый проект под названием itfinder:
django-admin startproject itfinder
Проект состоит из двух приложений – «Проекты» и «Пользователи». Начнем работу с создания скелета проекта projects:
cd itfinder python manage.py startapp projects
Django автоматически создает базовую структуру приложения, но зарегистрировать projects в конфигурационном файле проекта придется вручную. Откройте файл itfinder\settings.py, и добавьте в список INSTALLED_APPS строку 'projects.apps.ProjectsConfig'
.
Теперь нужно создать основные маршруты приложения «Проекты». Создайте файл projects/urls.py и сохраните в нем нужные маршруты:
from django.urls import path from . import views urlpatterns = [ path('', views.projects, name="projects"), path('project-object/<str:pk>/', views.project, name="project"), ]
Чтобы маршруты projects были доступны на уровне проекта, нужно включить их в глобальные URL – в файле itfinder\urls.py:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('projects.urls')), ]
Шаблоны приложения Projects
Создайте папку templates внутри projects, и в ней два файла – projects.html и single-project.html. Тестовый код для шаблона projects.html:
<!DOCTYPE html> <html> <head> <title>Шаблон для вывода проектов</title> </head> <body> <h2>Здесь будут все проекты</h2> {% for project in projects %} <p>Номер проекта: #{{ project.id }}</p> <h3>Название проекта: <a href="{% url 'project' project.id %}">{{ project.title }}</a></h3> <em>{{ project.description }}</em> {% endfor %} </body> </html>
Для single-projects.html:
<!DOCTYPE html> <html> <head> <title>Шаблон для отдельного проекта</title> </head> <body> <h1>Подробная информация о проекте</h1> <h3>Проект #{{ project.id }}</h3> <p>{{ project.title }}</p> <br> <em>{{ project.description }}</em> </body> </html>
Добавим import os
и ссылку на шаблоны в файл настроек itfinder/settings.py:
'DIRS': [os.path.join(BASE_DIR, 'templates')],
Принцип работы Django основан на схеме MVT – (Модель, Представление, Шаблон). Если вам не приходилось раньше работать с Джанго, схему работы фреймворка может прояснить приведенный ниже пример того, как функция представления передает данные в шаблон, который мы только что создали. Напишем тестовые функции представления в файле projects/views.py:
from django.shortcuts import render def projects(request): projectsList = [{'id':'1', 'title':'Онлайн-кинотеатр', 'description':'Кинотеатр с самой полной библиотекой фильмов.'}, {'id':'2', 'title':'Платформа с ИТ-курсами', 'description':'Курсы по фронтенду, бэкенду и мобильной разработке.'}, {'id':'3', 'title':'Рекрутинговый портал', 'description':'Вакансии для специалистов экстра-класса.'}, ] return render(request, 'projects/projects.html', {'projects':projectsList}) def project(request): return render(request, 'projects/single-project.html')
Запустите сервер python manage.py runserver
и откройте адреса http://localhost:8000/ и http://localhost:8000/project/ – все работает, функция передает в шаблон тестовые данные:
В реальных приложениях модели извлекают информацию из базы данных, а представления могут быть основаны не на функциях, а на классах.
База данных
Джанго по умолчанию работает с базой типа SQLite3 – настройки для подключения указаны в файле itfinder/settings.py. Для инициализации базы данных выполним команду python manage.py migrate
. Эта же команда используется для применения изменений, в дальнейшем мы будем ею пользоваться неоднократно, но уже в другом формате – после команды python manage.py makemigrations
, которая отслеживает все изменения в моделях.
После инициализации базы Джанго открывает доступ к панели администрирования – http://localhost:8000/admin. Однако для создания учетной записи администратора (суперпользователя) нужно выполнить еще одну команду:
python manage.py createsuperuser
В ходе создания аккаунта можно пропустить введение имени – тогда по умолчанию будет назначен юзернейм user, и емейла (поле останется пустым). Если сейчас войти в панель администрирования, можно отметить, что пока там можно создавать, изменять и удалять только новых пользователей и группы.
Для создания, редактирования и удаления других типов записей (профилей, проектов, комментариев) нужно сначала определить, а затем зарегистрировать в админ-панели нужные модели. Все атрибуты таблиц базы данных и взаимоотношения между ними определяются в файлах типа models.py. Модели представляют собой классы, все атрибуты которых ORM слой Django связывает с соответствующими полями таблиц. Благодаря этому отпадает необходимость использовать язык SQL.
Создадим нашу первую модель, которая будет хранить данные обо всех проектах, добавленных в раздел «Проекты». Это первый вариант модели, в дальнейшем мы ее расширим и дополним:
from django.db import models import uuid class Project(models.Model): title = models.CharField(max_length=100) slug = models.SlugField() description = models.TextField(null=True, blank=True) demo_link = models.CharField(max_length=500, null=True, blank=True) source_link = models.CharField(max_length=500, null=True, blank=True) created = models.DateTimeField(auto_now_add=True) id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False) def __str__(self): return self.title
После создания модели нужно подготовить миграции:
python manage.py makemigrations
И применить их к базе:
python manage.py migrate
Чтобы модель была доступна в панели администрирования, ее нужно зарегистрировать в файле itfinder/admin.py:
from django.contrib import admin from .models import Project admin.site.register(Project)
После регистрации форма добавления проектов становится доступной в панели администрирования:
В нашем приложении будет несколько моделей, связанных друг с другом различными способами:
- Один к одному – у одного пользователя может быть только один профиль.
- Один ко многим – один пользователь (профиль) может быть автором множества проектов; один проект может получить множество отзывов / комментариев / оценок. Для реализации такой связи служит
ForeignKey
. Установление связи между таблицами с помощью ForeignKey нуждается в дополнительных параметрах на случай удаления одной из таблиц. В случае с проектом и отзывами к нему этоCASCADE
– он удаляет отзыв в случае удаления проекта, к которому отзыв относился. - Многие ко многим – у одного проекта может быть множество тегов, а у пользователя – несколько навыков. Один и тот же тег (навык) может использоваться во множестве проектов (или профилей).
Связь проекта с тегами и отзывами
Расширим модель Project, добавив в нее связь с таблицами тегов и отзывов:
from django.db import models import uuid class Tag(models.Model): name = models.CharField(max_length=100) slug = models.SlugField() created = models.DateTimeField(auto_now_add=True) id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False) def __str__(self): return self.name class Project(models.Model): title = models.CharField(max_length=100) slug = models.SlugField() description = models.TextField(null=True, blank=True) tags = models.ManyToManyField(Tag, blank=True) total_votes = models.IntegerField(default=0, null=True, blank=True) votes_ratio = models.IntegerField(default=0, null=True, blank=True) demo_link = models.CharField(max_length=500, null=True, blank=True) source_link = models.CharField(max_length=500, null=True, blank=True) created = models.DateTimeField(auto_now_add=True) id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False) def __str__(self): return self.title class Review(models.Model): project = models.ForeignKey(Project, on_delete=models.CASCADE) VOTE_TYPE = ( ('up', 'Up Vote'), ('down', 'Down Vote'), ) review_text = models.TextField(null=True, blank=True) value = models.CharField(max_length=200, choices=VOTE_TYPE) created = models.DateTimeField(auto_now_add=True) id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False) def __str__(self): return self.value
Зарегистрируем новые модели в admin.py:
from django.contrib import admin from .models import Project, Review, Tag admin.site.register(Project) admin.site.register(Review) admin.site.register(Tag)
Выполним миграции и откроем админ-панель – теперь к проектам можно добавлять теги, рейтинг и отзывы:
Создайте несколько проектов или воспользуйтесь готовой базой данных. Эта информация нам понадобится для реализации следующего шага – запросов.
Запросы к базе данных
Структура запроса к базе данных в Джанго выглядит так:
Queryset = ModelName.objects.all()
- где Queryset – переменная, которой передаются результаты запроса;
- ModelName – название модели;
- objects – атрибут объекта модели;
- all() – метод.
Кроме all(), который возвращает все объекты модели, есть множество других методов, например:
- filter, filter_by – фильтруют по значениям атрибутов. При этом значение может быть равно запросу, содержать ключевое слово или начинаться с определенного критерия. Фильтры могут учитывать или игнорировать регистр.
- icontains – проверяет, содержит ли поле определенный запрос.
- get – возвращает единичный объект;
- exclude – исключает определенные значения.
Тестировать запросы удобно в интерактивной консоли, которую запускают командой python manage.py shell
:
python manage.py shell >>> from projects.models import Project >>> projects = Project.objects.all() >>> print(projects) <QuerySet [<Project: EverGreen - магазин люксовой эко-косметики>, <Project: ITGi gs - платформа для поиска ИТ-фрилансеров>, <Project: BlahBlahChat - социальная с еть нового поколения>, <Project: RoboShop - магазин электронных игрушек>, <Proje ct: SuperCook - сайт для кулинарного блогера>]> >>> singleproject = Project.objects.get(title='RoboShop - магазин электронных иг рушек') >>> print(singleproject) RoboShop - магазин электронных игрушек >>> print(singleproject.created) 2022-07-05 15:40:34.240616+00:00 >>> print(singleproject.slug) roboshop-magazin-igrushek >>> exit()
Передача данных из базы в шаблон
Откройте файл itfinder/views.py, добавьте в него импорт моделей:
from .models import Project
И измените функцию представления projects на эту:
def projects(request): projects = Project.objects.all() context = {'projects': projects} return render(request, 'projects/projects.html', context)
Функция представления отдельного проекта теперь должна выглядеть так:
def project(request, pk): projectObj = Project.objects.get(id=pk) tags = projectObj.tags.all() return render(request, 'projects/single-project.html', {'project': projectObj})
В шаблон для вывода отдельных проектов нужно добавить отображение тегов:
<!DOCTYPE html> <html> <head> <title>Шаблон для отдельного проекта</title> <meta charset="UTF-8"> </head> <body> <h1>Подробная информация о проекте</h1> <h3>Проект {{ project.id }}</h3> <p>{{ project.title }}</p> <br> <ul> {% for tag in project.tags.all %} <li>{{ tag }}</li> {% endfor %} </ul> <em>{{ project.description }}</em> </body> </html>
Запустите сервер – на главной странице сайта появилось описание проектов из базы данных:
И описание отдельного проекта тоже выводится – с тегами:
Код и база данных для первого этапа – здесь.
Второй этап
Начнем с разработки CRUD – функциональности для создания, редактирования и удаления проектов.
Сначала сделаем шаблон для формы, которая будет использоваться как для создания, так и для редактирования проектов. Сохраните этот код в project_form.html:
{% block content %} <h1>Информация о проекте</h1> <form method="POST"> {% csrf_token %} {{ form.as_p }} <input type="submit" name="" value="Добавить проект"> </form> {% endblock content %}
Токен csrf_token обеспечивает защиту сайта от атак; кроме того, без него форма просто не будет обрабатываться. Параметр .as_p
создает теги <p></p>
вокруг элементов формы.
Создайте файл itfinder\forms.py:
from django.forms import ModelForm from .models import Project class ProjectForm(ModelForm): class Meta: model = Project fields = ['title', 'slug', 'tags', 'description', 'demo_link', 'source_link']
Во многих случаях вместо строки с перечислением полей можно указать fields = '__all__'
– тогда модуль ModelForm автоматически сгенерирует форму на основе модели проекта, и перечислять поля по отдельности не придется. При этом модуль проигнорирует поля, которые не подлежат редактированию (как уникальный UUID), и пропустит поля, создаваемые во время сохранения записи (как created
). Однако в нашей модели Project есть поля, которые обновляются при подсчете рейтинга, и пользователи не должны иметь доступа к ним, поэтому в форме мы перечисляем только редактируемые поля.
Добавьте ссылку на создание проекта в шаблон projects.html:
<strong><a href="{% url 'create-project' %}">Добавить проект</a></strong>
Перезагрузите страницу – форма создания нового проекта благополучно загружается, хотя пока что не имеет никакого дизайна (и ничего не сохраняет):
Чтобы форма сохраняла данные, внесем дополнения в itfinder/views.py - добавим импорт redirect и обработку POST:
def createProject(request): form = ProjectForm() if request.method == 'POST': form = ProjectForm(request.POST) if form.is_valid(): form.save() return redirect('projects')
Теперь данные сохраняются – запись добавлена в список проектов. Реализовать редактирование проектов несложно. Сначала добавим новую функцию в views.py:
def updateProject(request, pk): project = Project.objects.get(id=pk) form = ProjectForm(instance=project) if request.method == 'POST': form = ProjectForm(request.POST, instance=project) if form.is_valid(): form.save() return redirect('projects') context = {'form': form} return render(request, 'projects/project_form.html', context)
Параметр instance=project
здесь обеспечивает предзаполнение формы существующими в базе данными:
Теперь сделаем нужный маршрут в urls.py:
path('update-project/<str:pk>', views.updateProject, name="update-project"),
И добавим ссылку на редактирование в шаблон projects.html:
<h4><a href="{% url 'update-project' project.id %}">Редактировать проект</a></h4>
Для удаления проектов (а в дальнейшем – и других объектов) нужно сделать шаблон, запрашивающий подтверждение действий пользователя. Создайте файл delete.html и сохраните в нем код:
{% block content %} <form action="" method="POST"> {% csrf_token %} <p>Вы действительно хотите удалить "{{ object }}"?</p> <a href="{% url 'projects' %}">Отмена</a> <input type="submit" name="" value="Подтвердить удаление" /> </form> {% endblock %}
В файл views.py добавьте функцию удаления:
def deleteProject(request, pk): project = Project.objects.get(id=pk) if request.method == 'POST': project.delete() return redirect('projects') context = {'object': project} return render(request, 'projects/delete.html', context)
На этом работа по созданию CRUD закончена, перейдем к дизайну и статическим файлам.
Статические файлы
Создайте папку static в корневой директории проекта. Внутри static создайте 3 папки:
- styles – в ней будут находиться css файлы. Поместите туда main.css.
- js – для скриптов. Положите туда этот скрипт.
- uikit – для стилей фронтенда. Там должны быть все эти файлы.
Чтобы Джанго знал, откуда загружать стили, нужно внести дополнения в файл настроек settings.py:
STATICFILES_DIRS = [ BASE_DIR / 'static' ]
Помимо шаблонов, находящихся в папке projects, нам понадобится набор шаблонов, определяющих внешний вид проекта на, так сказать, глобальном уровне. Для этих шаблонов нужно создать папку templates в корневой директории проекта itfinder. Сейчас в эту папку нужно поместить эти два файла – base.html и navbar.html, позже мы добавим туда еще несколько шаблонов.
Шаблоны в папке templates/projects нужно заменить на эти – они соответствуют готовой версии приложения и содержат ссылки на атрибуты моделей, которые мы разработаем на последующих этапах.
Последнее, что мы сделаем на этом этапе – добавим фильтр проектов по тегам. Для этого в views.py нужно добавить эту функцию:
def projects_by_tag(request, tag_slug): tag = get_object_or_404(Tag, slug=tag_slug) projects = Project.objects.filter(tags__in=[tag]) context = { "projects": projects } return render(request, "projects/projects.html", context)
В файл urls.py надо вставить маршрут:
path('tag/<slug:tag_slug>', views.projects_by_tag, name="tag"),
В шаблон projects.html добавить этот код:
<div class="project__tags"> {% for tag in project.tags.all %} <a href="{% url 'tag' tag.slug %}" class="tag tag--pill tag--main">{{tag}}</a> {% endfor %} </div>
А в шаблон single-project.html этот:
<div class="singleProject__toolStack"> {% for tag in project.tags.all %} <a href="{% url 'tag' tag.slug %}" class="tag tag--pill tag--sub tag--lg">{{tag}}</a> {% endfor %} </div>
Запустим сервер – наш проект получил дизайн и обзавелся фильтрацией по тегам, причем фильтр работает как на главной странице, так и на странице отдельного проекта:
Весь код для этого этапа можно взять здесь.
В следующей части мы реализуем добавление изображений для проектов с фронтенда, а также разработаем основную функциональность второго приложения, Users.
Материалы по теме
- 🐍🚀 Django с нуля. Часть 1: пишем многопользовательский блог для клуба любителей задач Python
- 🐍🚀 Django с нуля. Часть 2: регистрация, авторизация, ограничение доступа
- 🐍🚀 Django с нуля. Часть 3: создание профилей, сжатие изображений, CRUD и пагинация