Третий этап
Основная часть работы над приложением «Проекты» была выполнена на предыдущем этапе – осталось реализовать добавление изображений. Для этого нужно внести дополнения в модель 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 }} ×</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
Запустите сервер – можно создавать профили в админ-панели. На главной странице теперь выводятся профили разработчиков:

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

Четвертый этап
Для связи проектов с профилями разработчиков добавим импорт модели профиля в projects/models.py – from 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.
Комментарии