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

В этой части: добавление обложек проектов с фронтенда, создание системы авторизации, разработка основной функциональности приложения «Пользователи» и связь проектов с профилями.
🐍🚀 Создаем рекрутинговый портал на Django: часть 2

Хочешь уверенно проходить IT-интервью?

Готовься к IT-собеседованиям уверенно с AI-тренажёром T1!

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.

💡 Почему Т1 тренажёр — это мастхэв?

  • Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
  • Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
  • Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.

Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!

Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy


Третий этап

Основная часть работы над приложением «Проекты» была выполнена на предыдущем этапе – осталось реализовать добавление изображений. Для этого нужно внести дополнения в модель Projects:

        image = models.ImageField(null=True, blank=True, default="project_images/default.jpg", upload_to='project_images')
    

А затем выполнить миграции:

        python manage.py makemigrations
python manage.py migrate

    

По умолчанию Джанго будет сохранять изображения прямо в корневую директорию проекта, а искать – в папке static. Гораздо удобнее завести для изображений отдельную папку media в корне проекта (а не внутри static). Создайте директорию media, внутри нее папку project_images и поместите в нее файл default.jpg.

Чтобы дать Джанго знать, куда сохранять изображения, сделаем нужные настройки в settings.py:

        MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

    

В файл urls.py добавим импорт:

        from django.conf.urls.static import static
    

И маршруты к нужным папкам:

        urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

    

Осталось добавить ссылки на изображения в шаблон projects.html:

        <img class="project__thumbnail" src="{{ project.image.url }}" alt="скриншот проекта" />
    

И в single-project.html:

        <img class="singleProject__preview" src="{{ project.image.url }}" alt="скриншот проекта" />
    
Изображение на странице проекта
Изображение на странице проекта

Пока что добавлять изображения в карточки проектов можно только в панели администрирования. Чтобы дать пользователям возможность добавлять изображения на стороне фронтенда, сделаем новый шаблон project_form.html:

        {% extends 'base.html' %}
{% block content %}
 
<main class="formPage my-xl">
 <div class="content-box">
     <div class="formWrapper">
         <a class="backButton" href="#"><i class="im im-angle-left"></i></a>
          <br>
          <form class="form" method="POST" enctype="multipart/form-data">
           {% csrf_token %}
           {% for field in form %}
           <div class="form__field">
            <label for="formInput#text">{{ field.label }}</label>
                 {{ field }}
           </div>
           {% endfor %}
           <div class="form__field">
            {% for tag in project.tags.all %}
             <div class="project-tag tag tag--pill tag--main" data-tag="{{ tag.id }}"
               data-project="{{ project.id }}">{{ tag.name }} &#215;</div>
            {% endfor %}
            </div>
            <div class="form__field">
             <label for="formInput#text">Теги</label>
             <textarea class="input" name="newtags" placeholder="Добавьте собственные теги, если их нет в списке"></textarea>
             </div>
             <input class="btn btn--sub btn--lg  my-md" type="submit" value="Сохранить" />
          </form>
    	 </div>
    </div>
</main>
{% endblock %}

    

Кроме того, внесем дополнения в forms.py:

        from django.forms import ModelForm
from django import forms
from .models import Project
 
class ProjectForm(ModelForm):
    class Meta:
    model = Project
    fields = ['title', 'slug', 'image', 'tags', 'description', 'demo_link', 'source_link']
    labels = {
         'title': 'Название проекта',
         'slug': 'Слаг',
         'image':'Скриншот проекта',
         'tags':'Теги',
         'description':'Описание проекта',
         'demo_link':'Демо-версия',
         'source_link':'Исходный код на GitHub'
         }
 
     widgets = {
         'tags': forms.CheckboxSelectMultiple(),
    	}
 
     def __init__(self, *args, **kwargs):
         super(ProjectForm, self).__init__(*args, **kwargs)
         for name, field in self.fields.items():
             field.widget.attrs.update({'class': 'input'}) 

    

Для сохранения изображений с фронтенда добавим FILES в функции createProject и updateProject:

        def createProject(request):
     form = ProjectForm()
     if request.method == 'POST':
         form = ProjectForm(request.POST, request.FILES)
         if form.is_valid():
             form.save()
             return redirect('projects')
     context = {'form': form}
     return render(request, 'projects/project_form.html', context)
def updateProject(request, pk):
     project = Project.objects.get(id=pk)
     form = ProjectForm(instance=project)
     if request.method == 'POST':
         form = ProjectForm(request.POST, request.FILES, instance=project)
         if form.is_valid():
             form.save()
             return redirect('projects')
     context = {'form': form}
     return render(request, 'projects/project_form.html', context)

    

Создать проект можно по прежней ссылке – http://localhost:8000/create-project/, однако в навигационную панель мы эту ссылку добавлять не станем – позже она будет доступна в профиле пользователя.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»

Приложение Users – «Пользователи»

Начнем разработку секции «Пользователи» с создания нового приложения:

        python manage.py startapp users
    

Как и в случае с projects, приложение нужно зарегистрировать в INSTALLED_APPS (settings.py):

        'users.apps.UsersConfig'
    

Для вывода профилей нам понадобятся два шаблона – profiles.html и user-profile.html, поместите их в папку users/templates/users.

Теперь создадим нужные функции представления – для вывода всех профилей, просмотра отдельного профиля и фильтрации профилей по скиллам. Сохраните этот код в файле users/views.py:

        from django.shortcuts import render, get_object_or_404
from .models import Profile, Skill
 
def profiles(request):
    profiles = Profile.objects.all()
    context = {'profiles': profiles}
    return render(request, 'users/profiles.html', context)
 
 
def userProfile(request, pk):
    profile = Profile.objects.get(id=pk)
    main_skills = profile.skills.all()[:2]
    extra_skills = profile.skills.all()[2:]
    context = {'profile': profile, 'main_skills': main_skills,
           	"extra_skills": extra_skills}
    return render(request, 'users/user-profile.html', context)
 
def profiles_by_skill(request, skill_slug):
    skill = get_object_or_404(Skill, slug=skill_slug)
    profiles = Profile.objects.filter(skills__in=[skill])
    context = {
    	"profiles": profiles
	}
    return render(request, "users/profiles.html", context)

    

Создайте файл users/urls.py и сохраните в нем эти маршруты:

        from django.urls import path
from . import views
 
urlpatterns = [
    path('', views.profiles, name="profiles"),
    path('profile/<str:pk>/', views.userProfile, name="user-profile"),
    path('skill/<slug:skill_slug>', views.profiles_by_skill, name="skill"),
]

    

Внесите изменения в urlpatterns в главном urls.py в корневой директории проекта itfinder:

        path('projects/', include('projects.urls')),
path('', include('users.urls')),

    

Измените ссылку на главную страницу в шаблоне navbar.html – вставьте {% url 'profiles' %}.

И, наконец, сохраните нужные модели в users/models.py:

        from django.db import models
from django.contrib.auth.models import User
import uuid
 
class Skill(models.Model):
    name = models.CharField(max_length=50, blank=True, null=True)
    slug = models.SlugField()
    description = models.TextField(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 str(self.name)
 
 
class Profile(models.Model):
    user = models.OneToOneField(
        User, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length=50, blank=True, null=True)
    email = models.EmailField(max_length=50, blank=True, null=True)
    username = models.CharField(max_length=50, blank=True, null=True)
    city = models.CharField(max_length=50, blank=True, null=True)
    intro = models.CharField(max_length=200, blank=True, null=True)
    bio = models.TextField(blank=True, null=True)
    image = models.ImageField(
        null=True, blank=True, upload_to='profile_images', default="profile_images/default.jpg")
    skills = models.ManyToManyField(Skill, blank=True)
    github = models.CharField(max_length=100, blank=True, null=True)
    twitter = models.CharField(max_length=100, blank=True, null=True)
    linkedin = models.CharField(max_length=100, blank=True, null=True)
    youtube = models.CharField(max_length=100, blank=True, null=True)
    website = models.CharField(max_length=100, blank=True, null=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 str(self.username)
 
    class Meta:
        ordering = ['created']

    

Теперь создайте и примените миграции:

        python manage.py makemigrations
python manage.py migrate

    

Запустите сервер – можно создавать профили в админ-панели. На главной странице теперь выводятся профили разработчиков:

Профили на главной странице ITfinder
Профили на главной странице ITfinder

Карточки в разделе «Проекты» пока не связаны с их авторами – мы займемся этим на следующем этапе:

Проекты
Проекты

Четвертый этап

Для связи проектов с профилями разработчиков добавим импорт модели профиля в projects/models.pyfrom users.models import Profile и внесем дополнения в модель проекта:

        owner = models.ForeignKey(Profile, null=True, blank=True, on_delete=models.CASCADE)
    

Подготовьте и выполните миграции:

        python manage.py makemigrations
python manage.py migrate

    

В панели управления выберите разработчиков-авторов для проектов, а в шаблонах projects/projects.html и projects/single-project.html вставьте ссылку на автора:

        {% url 'user-profile' project.owner.id %}
    

Запустите сервер и перейдите на страницу «Проекты» – у каждого проекта теперь есть автор, и в профилях разработчиков отображаются их проекты:

Проект разработчика в профиле
Проект разработчика в профиле

Сигналы

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

Создайте файл users/signals.py и сохраните в нем этот код, а в файл users/apps.py внесите это дополнение:

        def ready(self):
    import users.signals
    

Теперь можно перейти к созданию всей нужной функциональности для учетных записей.

Регистрация, аутентификация, права доступа

Аутентификация подтверждает личность пользователя, а система авторизации предоставляет (или ограничивает) доступ к определенным функциям и разделам сайта, в зависимости от того зарегистрирован ли пользователь или нет.

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

Добавьте в users/urls.py маршруты:

        path('login/', views.loginUser, name="login"),
path('logout/', views.logoutUser, name="logout"),
path('register/', views.registerUser, name="register"),  
path('account/', views.userAccount, name="account"),
path('edit-account/', views.editAccount, name="edit-account"),
path('create-skill/', views.createSkill, name="create-skill"),
path('update-skill/<slug:skill_slug>/', views.updateSkill, name="update-skill"),
path('delete-skill/<slug:skill_slug>/', views.deleteSkill, name="delete-skill"),

    

В шаблон navbar.html добавьте ссылки на аккаунт, вход/регистрацию, выход:

        {% url 'account' %}
{% url 'login' %}
{% url 'logout' %}

    

Доступ для авторизованных пользователей

Декоратор @login_required(login_url='login') обеспечивает доступ к определенным функциям только для зарегистрированных и авторизованных пользователей. Сохраните эти представления в файле users/views.py:

        @login_required(login_url='login')
def userAccount(request):
    profile = request.user.profile
 
    skills = profile.skills.all()
    projects = profile.project_set.all()
 
    context = {'profile': profile, 'skills': skills, 'projects': projects}
    return render(request, 'users/account.html', context)
 
 
@login_required(login_url='login')
def editAccount(request):
    profile = request.user.profile
    form = ProfileForm(instance=profile)
 
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save()
 
            return redirect('account')
 
    context = {'form': form}
    return render(request, 'users/profile_form.html', context)
 
 
@login_required(login_url='login')
def createSkill(request):
    profile = request.user.profile
    form = SkillForm()
 
    if request.method == 'POST':
        form = SkillForm(request.POST)
        if form.is_valid():
            skill = form.save(commit=False)
            skill_slug = request.POST.get('slug')
            skill_description = request.POST.get('description')
            profile.skills.get_or_create(name=skill, slug=skill_slug, description=skill_description)
            messages.success(request, 'Навык добавлен')
            return redirect('account')
 
    context = {'form': form}
    return render(request, 'users/skill_form.html', context)
 
 
@login_required(login_url='login')
def updateSkill(request, skill_slug):
    profile = request.user.profile
    skill = profile.skills.get(slug=skill_slug)
    form = SkillForm(instance=skill)
 
    if request.method == 'POST':
        form = SkillForm(request.POST, instance=skill)
        if form.is_valid():
            form.save()
            messages.success(request, 'Навык успешно обновлен')
            return redirect('account')
 
    context = {'form': form}
    return render(request, 'users/skill_form.html', context)
 
 
@login_required(login_url='login')
def deleteSkill(request, skill_slug):
    profile = request.user.profile
    skill = profile.skills.get(slug=skill_slug)
    if request.method == 'POST':
        skill.delete()
        messages.success(request, 'Навык успешно удален')
        return redirect('account')
 
    context = {'object': skill}
    return render(request, 'delete_template.html', context)

    

Помимо функций userAccount, editAccount, createSkill, updateSkill, deleteSkill мы добавим декоратор к функциям в файле views.py в приложении «Проекты» – поскольку добавлять, редактировать и удалять проекты могут только авторизованные пользователи. Функции createSkill, updateSkill, deleteSkill обеспечивают добавление, редактирование и удаление пользователями скиллов в своих профилях:

Пользователи могут редактировать любые данные
Пользователи могут редактировать любые данные

Удаление скиллов и проектов обрабатывает шаблон delete_template.html – его нужно поместить в папку с глобальными шаблонами templates. Кроме того, создайте новый файл users/forms.py, содержащий нужные формы.

Чтобы включить юзернеймы разработчиков в URL, в шаблонах templates/users мы заменим project.owner.id на project.owner, a маршрут в urls.py на этот:

        path('profile/<str:username>/', views.userProfile, name="user-profile"),
    

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

        {% url 'account' %}
    

Автоматическая генерация слагов

Если пользователь решит добавить к своему проекту теги, которых еще нет в базе, для них нужно будет сгенерировать слаги. Для этого добавьте импорт в projects/models.py:

        from django.utils.text import slugify
    

И эту функцию в класс Tag:

            def save(self, *args, **kwargs):
        value = self.name
        self.slug = slugify(value, allow_unicode=True)
        super().save(*args, **kwargs)
 
    

Весь код и тестовый контент для этого этапа – в репозитории ITfinder.

***

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

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию

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