🐍🚀 Создаем рекрутинговый портал на Django: часть 1

Разрабатываем портал, на котором каждый рекрутер сможет найти своего разработчика :). Система поиска позволяет подобрать нужного специалиста по резюме, портфолио или описанию проектов; рейтинг проекта поможет оценить квалификацию, а мессенджер – послать оффер.
🐍🚀 Создаем рекрутинговый портал на Django: часть 1

Это достаточно сложный проект, состоящий из двух взаимосвязанных приложений – Projects и Users. Разработчики могут создавать профили и добавлять в портфолио проекты. Предусмотрены поиск, оценка проектов и обмен сообщениями. Реализована фильтрация по тегам (для проектов) и по скиллам (для профилей). Весь код проекта ITfinder находится здесь.

Обзор проекта

Сайт включает:

  1. Систему аутентификации и авторизации.
  2. Поиск по профилям, навыкам, тегам, описаниям проектов.
  3. Фильтрацию по тегам и скиллам.
  4. CRUD операции для создания, редактирования, удаления профилей, проектов, тегов и скиллов.
  5. Мессенджер.
  6. Систему оценок и отзывов о проектах.
Главная страница – профили разработчиков
Главная страница – профили разработчиков
Раздел "Проекты"
Раздел "Проекты"
Профиль разработчика
Профиль разработчика
Страница проекта с отзывами
Страница проекта с отзывами
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»

Первый этап

Работа над 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)

    

После регистрации форма добавления проектов становится доступной в панели администрирования:

Теперь можно добавлять проекты
Теперь можно добавлять проекты

В нашем приложении будет несколько моделей, связанных друг с другом различными способами:

  1. Один к одному – у одного пользователя может быть только один профиль.
  2. Один ко многим – один пользователь (профиль) может быть автором множества проектов; один проект может получить множество отзывов / комментариев / оценок. Для реализации такой связи служит ForeignKey. Установление связи между таблицами с помощью ForeignKey нуждается в дополнительных параметрах на случай удаления одной из таблиц. В случае с проектом и отзывами к нему это CASCADE – он удаляет отзыв в случае удаления проекта, к которому отзыв относился.
  3. Многие ко многим – у одного проекта может быть множество тегов, а у пользователя – несколько навыков. Один и тот же тег (навык) может использоваться во множестве проектов (или профилей).

Связь проекта с тегами и отзывами

Расширим модель 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>
    

Запустим сервер – наш проект получил дизайн и обзавелся фильтрацией по тегам, причем фильтр работает как на главной странице, так и на странице отдельного проекта:

Фильтр по тегу Angular
Фильтр по тегу Angular

Весь код для этого этапа можно взять здесь.

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

***

Материалы по теме


Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ