В этом уроке мы создадим приложение для рецептов на Django, где пользователи смогут зарегистрироваться и создавать, читать, обновлять и удалять рецепты. Ниже я также приведу ссылки на видео 📽️, связанные с каждым шагом.
- Конечный продукт:
- Шаг 1: Настройка проекта
- 🍾🎉 Ух ты, наш проект Django запущен и работает!
- Шаг 2: Приложения, URL и представления Django
- Запустите сервер снова, зайдите на localhost:8000 и вы должны увидеть ‘Welcome to the Recipes home page’ 🚀.
- Шаг 3: HTML шаблоны + пользовательский интерфейс
- Теперь вернитесь на главную страницу (запустите сервер!) и вы должны увидеть отображение данных рецептов 🍕🍟🍔.
- Шаг 4: Стиль Bootstrap
- Теперь у нас есть навигация, и она выглядит немного лучше 🦋
- Шаг 5: Настройка администратора Django
- Админка django — это удобный инструмент при разработке и даже в реальной жизни для добавления/редактирования/обновления/удаления данных.
- Шаг 6: Создание модели базы данных
- Удивительно — теперь у нас есть реальные данные, которые хранятся и отображаются на нашем сайте!
- Шаг 7: Регистрация пользователей 📝
- Теперь новые пользователи могут зарегистрироваться!
- Шаг 8: Вход и выход
- Потрясающе! Теперь наше приложение имеет полную авторизацию пользователей, пользователи могут регистрироваться, входить и выходить!
- Шаг 9: Рецепты CRUD:
- У вас все получилось! Теперь у вас есть приложение для приготовления сладких рецептов, которое вы можете настроить по своему вкусу или использовать для создания чего-то нового!
Конечный продукт:
Шаг 1: Настройка проекта
К концу этого шага у нас будет запущен проект Django. Мы будем использовать python 3.8. Все, что выше 3.3, должно работать.
-
Создайте виртуальную среду для проекта, чтобы его пакеты были отделены от других на вашем компьютере. В терминале введите:
Windows:Mac:
-
Далее нам нужно установить Django, в терминале введите:
-
Создайте проект Django. Каждый проект Django может состоять из 1 или более приложений, в вашем терминале (символ ‘.’ создает в текущем каталоге):
-
Запустите сервер и просмотрите localhost:8000. В терминале наберите (введите ctr+c для остановки сервера):
🍾🎉 Ух ты, наш проект Django запущен и работает!
Шаг 2: Приложения, URL и представления Django
К концу этого шага у нас будет веб-страница с текстом!
1) Создайте ваше первое приложение Django, в терминале введите:
2) Откройте config/settings.py и добавьте ‘recipes’ в раздел установленных приложений.
# config/settings.py
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
#local apps
'recipes',
]
3) Давайте создадим представление, чтобы вернуть что-то пользователю. Откройте файл recipes/views.py, импортируйте HttpResponse и создайте функцию home(request), которая возвращает HttpResponse с некоторым текстом:
# recipes/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse('<h1>Welcome to the Recipes home page</h1>')
4) Создайте новый файл в папке рецептов ‘urls.py’ для маршрутизации. Импортируйте views.py из той же папки и добавьте новый путь к urlpatterns для домашнего маршрута:
# recipes/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name="recipes-home"),
]
5) Обновите файл urls.py в папке config, чтобы включить в него урлы из приложения recipes. в config/urls.py добавьте:
# config/urls.py
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('recipes.urls')),
]
Запустите сервер снова, зайдите на localhost:8000 и вы должны увидеть ‘Welcome to the Recipes home page’ 🚀.
Шаг 3: HTML шаблоны + пользовательский интерфейс
В этом шаге мы вернем настоящую html-страницу!
1) Создайте каталог templates для хранения файлов с таким путем: recipes/templates/recipes
2) В папке templates/recipes создайте файл ‘base.html’ и добавьте в него html, который будут использовать все наши страницы:
# /templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% if title %}
<title>Django Recipes - {{title}}</title>
{% else %}
<title>Django Recipes</title>
{% endif %}
</head>
<body>
{% block content %} {% endblock %}
</body>
</html>
3) Создайте еще один html-шаблон ‘home.html’ и добавьте html для главной страницы, давайте передадим некоторые фиктивные данные:
# templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<h1>{{recipe.title}}</h1>
<p>{{recipe.author}} | {{recipe.date_posted}}</p>
<h5>{{recipe.content}}</h5>
<hr />
{% endfor %}
<!-- -->
{% endblock content %}
4) Теперь давайте обновим вид главной страницы и передадим фиктивные данные. В файле recipes/views.py добавьте:
# recipes/views.py
recipes = [
{
'author': 'Dom V.',
'title': 'Meatballs',
'content': 'Combine ingredients, form into balls, brown, then place in oven.',
'date_posted': 'May 18th, 2022'
},
{
'author': 'Gina R.',
'title': 'Chicken Cutlets',
'content': 'Bread chicken, cook on each side for 8 min',
'date_posted': 'May 18th, 2022'
},
{
'author': 'Bella O.',
'title': 'Sub',
'content': 'Combine ingredients.',
'date_posted': 'May 18th, 2022'
}
]
# Create your views here.
def home(request):
context = {
'recipes': recipes
}
return render(request, 'recipes/home.html', context)
Теперь вернитесь на главную страницу (запустите сервер!) и вы должны увидеть отображение данных рецептов 🍕🍟🍔.
Шаг 4: Стиль Bootstrap
В этом шаге мы оформим сайт так, чтобы он выглядел немного лучше и имел некоторую навигацию 🧭.
1) Обновите файл templates/recipes/base.html, чтобы он загружал bootstrap css и js из cdn и имел базовую навигационную панель:
# templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{% if title %}
<title>Django Recipes - {{title}}</title>
{% else %}
<title>Django Recipes</title>
{% endif %}
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'recipes-home' %}">Recipes App</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a
class="nav-link"
aria-current="page"
href="{% url 'recipes-home' %}"
>Recipes</a
>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4 col-8">{% block content %} {% endblock %}</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"
integrity="sha384-kjU+l4N0Yf4ZOJErLsIcvOU2qSb74wXpOhqTvwVx3OElZRweTnQ6d31fXEoRD1Jy"
crossorigin="anonymous"
></script>
</body>
</html>
2) Придайте файлу home.html стилистику bootstrap, чтобы он выглядел красивее!
# templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<div class="card my-4">
<div class="card-body">
<h5 class="card-title">{{ recipe.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ recipe.author }}</h6>
<p class="card-text">{{ recipe.content }}</p>
<h6 class="card-subtitle mb-2 text-muted">{{ recipe.date_posted }}</h6>
<a href="#" class="card-link">View Recipe</a>
</div>
</div>
{% endfor %}
<!-- -->
{% endblock content %}
Теперь у нас есть навигация, и она выглядит немного лучше 🦋
Шаг 5: Настройка администратора Django
Django поставляется из коробки с интерфейсом администратора для работы с данными, это очень удобно. К концу этого шага мы настроим суперпользователя, который сможет редактировать данные.
1) Остановим сервер и в терминале создадим суперпользователя, но сначала нам нужно применить некоторые предварительно созданные миграции:
2) Теперь мы можем создать суперпользователя в терминале (введите учетные данные, когда появится запрос):
3) Запустите сервер и перейдите на localhost:8000/admin и войдите с учетными данными из шага 2.
4) Вы должны увидеть раздел ‘users’, где вы найдете суперпользователя, которого вы создали. Вы можете нажать ‘add’, чтобы создать другого пользователя.
Админка django — это удобный инструмент при разработке и даже в реальной жизни для добавления/редактирования/обновления/удаления данных.
Шаг 6: Создание модели базы данных
В этом разделе мы настроим базу данных для хранения данных и создадим некоторые данные через админку ⚙️.
1) Создайте модель рецептов в файле recipes/models.py:
# recipes/models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Recipe(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
2) Сделайте миграции для обновления базы данных, в терминале запустите:
3) Для того, чтобы увидеть модель в админке, нам нужно ее зарегистрировать, поэтому в файле recipes/admin.py добавьте:
# recipes/admin.py
from django.contrib import admin
from . import models
# Register your models here.
admin.site.register(models.Recipe)
4) Теперь запустите сервер, вернитесь на localhost:8000/admin, войдите в систему и добавьте несколько рецептов.
5) Давайте использовать эти реальные данные в нашем шаблоне, поэтому сначала обновите файл recipes/views.py home view для запроса к базе данных:
# recipes/views.py
from . import models
def home(request):
recipes = models.Recipe.objects.all()
context = {
'recipes': recipes
}
return render(request, 'recipes/home.html', context)
def about(request):
return render(request, 'recipes/about.html', {'title': 'about page'})
6) И давайте обновим шаблон home.html, чтобы использовать атрибуты, хранящиеся в модели:
# templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<div class="card my-4">
<div class="card-body">
<h5 class="card-title">{{ recipe.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ recipe.author }}</h6>
<p class="card-text">{{ recipe.description }}</p>
<h6 class="card-subtitle mb-2 text-muted">
{{ recipe.updated_at|date:"F d, Y" }}
</h6>
<a href="#" class="card-link">View Recipe</a>
</div>
</div>
{% endfor %}
<!-- -->
{% endblock content %}
Удивительно — теперь у нас есть реальные данные, которые хранятся и отображаются на нашем сайте!
Шаг 7: Регистрация пользователей 📝
В этом шаге мы сделаем так, чтобы пользователи могли регистрироваться на нашем сайте и создавать аккаунты!
1) Создайте новое приложение Django ‘users’, которое будет управлять этой функциональностью, в типе терминала:
2) Затем добавьте это приложение в наш файл config/settings.py:
# config/settings.py
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
#local apps
'recipes',
'users',
]
3) Создайте представление register в файле users/views.py для обработки регистрации новых пользователей (форму мы создадим позже):
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from . import forms
# Create your views here.
def register(request):
if request.method == "POST":
form = forms.UserRegisterForm(request.POST)
if form.is_valid():
form.save()
# cleaned data is a dictionary
username = form.cleaned_data.get('username')
messages.success(request, f"{username}, you're account is created!")
return redirect('recipes-home')
else:
form = forms.UserRegisterForm()
return render(request, 'users/register.html', {'form': form})
4) Теперь создадим форму, которую использует представление. в users/forms.py add:
#users/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
5) Настроим url-шаблон для регистрации, который создает представление из шага 1. Мы добавим его в урлы проекта, поэтому в config/urls.py добавьте:
# users/urls.py
...
from users import views as user_views
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('recipes.urls')),
path('register/', user_views.register, name="user-register"),
]
6) Создайте шаблон для формы регистрации, поэтому в приложении users создайте еще одну директорию по пути ‘users/templates/users’ и добавьте файл ‘register.html’ со следующей разметкой:
# users/templates/users/register.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Sign Up!</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group py-3">
<input class="btn btn-outline-primary" type="submit" value="Sign Up" />
</div>
</form>
<div class="border-top pt-3">
<a class="text-muted" href="#">Already have an account? Log in.</a>
</div>
</div>
<!-- -->
{% endblock content %}
7) Мы использовали crispy forms (сторонний пакет), чтобы формы выглядели лучше, поэтому нам нужно установить его в терминале:
8) Добавьте crispy forms к установленным приложениям в файл config/settings.py и добавьте еще одну переменную CRISPY_TEMPLATE_PACK.
# configs/settings.py file
INSTALLED_APPS = [
...
#local apps
'recipes',
'users',
# 3rd party
'crispy_forms',
]
...
CRISPY_TEMPLATE_PACK = 'bootstrap4'
Теперь новые пользователи могут зарегистрироваться!
Шаг 8: Вход и выход
На этом шаге мы завершим авторизацию пользователей, и они смогут регистрироваться, входить и выходить.
1) В urls проекта добавьте встроенные представления для входа и выхода. в ‘config/urls.py’ добавьте (мы также добавим url профиля, который мы добавим позже в этом шаге:
# config/urls.py
from django.contrib import admin
from django.urls import path, include
from users import views as user_views
from django.contrib.auth import views as auth_views
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('recipes.urls')),
path('register/', user_views.register, name="user-register"),
# new urls
path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name="user-login"),
path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name="user-logout"),
path('profile/', user_views.profile, name="user-profile"),
]
2) Давайте создадим шаблоны, которые мы передали в файле urls выше. Итак, в users/templates/users создадим файлы login.html и logout.html, в login.html добавим:
# users/templates/users/login.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Log In!</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group py-3">
<input class="btn btn-outline-primary" type="submit" value="Login" />
</div>
</form>
<div class="border-top pt-3">
<a class="text-muted" href="{% url 'user-register' %}"
>Don't have an account? Sign up.</a
>
</div>
</div>
<!-- -->
{% endblock content %}
3) И в logout.html добавьте:
# users/templates/users/logout.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<div class="container">
<h2>You have been logged out!</h2>
<div class="border-top pt-3">
<a class="text-muted" href="{% url 'user-login' %}">Log back in here.</a>
</div>
</div>
<!-- -->
{% endblock content %}
4) Обновите настройки проекта, чтобы перенаправлять пользователей до/после входа. Поэтому в config/settings.py добавьте:
# config/settings.py
...
LOGIN_REDIRECT_URL = 'recipes-home'
LOGIN_URL = 'user-login'
5) Обновите форму представления регистрации на последнем шаге, чтобы перенаправить пользователя на вход после регистрации. Поэтому в users/views.py обновите функцию регистрации (также создайте представление профиля):
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from . import forms
# Create your views here.
def register(request):
if request.method == "POST":
form = forms.UserRegisterForm(request.POST)
if form.is_valid():
form.save()
# cleaned data is a dictionary
username = form.cleaned_data.get('username')
messages.success(request, f"{username}, you're account is created, please login.")
return redirect('user-login')
else:
form = forms.UserRegisterForm()
return render(request, 'users/register.html', {'form': form})
@login_required()
def profile(request):
return render(request, 'users/profile.html')
6) Давайте обновим навигационную панель base.html, чтобы она ссылалась на различные действия в зависимости от того, вошел ли пользователь в систему или сейчас:
# recipes/templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{% if title %}
<title>Django Recipes - {{title}}</title>
{% else %}
<title>Django Recipes</title>
{% endif %}
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'recipes-home' %}">Recipes App</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a
class="nav-link"
aria-current="page"
href="{% url 'recipes-home' %}"
>Recipes</a
>
</li>
</ul>
</div>
<div class="navbar-nav">
{% if user.is_authenticated %}
<a class="nav-item nav-link" href="{% url 'user-profile' %}"
>My Profile</a
>
<a class="nav-item nav-link" href="{% url 'user-logout' %}">Logout</a>
{% else %}
<a class="nav-item nav-link" href="{% url 'user-login' %}">Login</a>
<a class="nav-item nav-link" href="{% url 'user-register' %}"
>Register</a
>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4 col-8">
{% if messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %} {% endif %} {% block content %} {% endblock %}
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"
integrity="sha384-kjU+l4N0Yf4ZOJErLsIcvOU2qSb74wXpOhqTvwVx3OElZRweTnQ6d31fXEoRD1Jy"
crossorigin="anonymous"
></script>
</body>
</html>
7) Наконец, давайте создадим шаблон профиля. Добавьте файл users/templates/users/profile.html и добавьте:
# users/templates/users/profile.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
<h1>{{ user.username}}</h1>
</div>
<!-- -->
{% endblock content %}
Потрясающе! Теперь наше приложение имеет полную авторизацию пользователей, пользователи могут регистрироваться, входить и выходить!
Шаг 9: Рецепты CRUD:
Теперь мы добавим последнюю функциональность — позволим пользователям создавать, обновлять и удалять рецепты. Таким образом, это будет полностью функционирующее веб-приложение! Мы также будем использовать представления на основе классов, что ускорит разработку для распространенных случаев использования.
1) Давайте обновим главную страницу, чтобы использовать представление на основе классов вместо функции. Поэтому обновим recipes/views.py и добавим этот класс:
# recipes/views.py
from django.shortcuts import render
from django.views.generic import ListView
from . import models
class RecipeListView(ListView):
model = models.Recipe
template_name = 'recipes/home.html'
context_object_name = 'recipes'
2) Обновите конфиг url рецептов, чтобы он указывал на этот класс. поэтому в recipes/urls.py обновите:
# recipes/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.RecipeListView.as_view(), name="recipes-home"),
]
3) Настроим детальное представление для каждого рецепта, добавим этот класс в файл recipes/views.py:
# recipes/views.py
...
from django.views.generic import ListView, DetailView,
...
class RecipeDetailView(DetailView):
model = models.Recipe
4) И настроим конфигурацию url, чтобы включить детальное представление (давайте просто добавим создание и удаление):
# recipes/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.RecipeListView.as_view(), name="recipes-home"),
path('recipe/<int:pk>', views.RecipeDetailView.as_view(), name="recipes-detail"),
path('recipe/create', views.RecipeCreateView.as_view(), name="recipes-create"),
path('recipe/<int:pk>/update', views.RecipeUpdateView.as_view(), name="recipes-update"),
path('recipe/<int:pk>/delete', views.RecipeDeleteView.as_view(), name="recipes-delete"),
]
5) Давайте создадим ожидаемый шаблон для страницы с подробным описанием рецепта. Создайте html-файл ‘recipes/templates/recipes/recipe_detail.html’ и добавьте следующую разметку:
# recipes/templates/recipes/reipe_detail.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipe # {{object.id}}</h1>
<div class="card my-4">
<div class="card-body">
<h5 class="card-title">{{ object.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ object.author }}</h6>
<p class="card-text">{{ object.description }}</p>
<h6 class="card-subtitle mb-2 text-muted">
{{ object.updated_at|date:"F d, Y" }}
</h6>
</div>
</div>
{% if object.author == user or user.is_staff %}
<div class="col-4">
<a class="btn btn-outline-info" href="{% url 'recipes-update' object.id %}">Update</a>
<a class="btn btn-outline-danger" href="{% url 'recipes-delete' object.id %}">Delete</a>
</div>
{% endif %}
<!-- -->
{% endblock content %}
6) Обновите html-шаблон главной страницы, чтобы он ссылался на страницу подробного описания каждого рецепта в файле recipes/templates/recipes/home.html:
# recipes/templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<div class="card my-4">
<div class="card-body">
<h5 class="card-title">{{ recipe.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ recipe.author }}</h6>
<p class="card-text">{{ recipe.description }}</p>
<h6 class="card-subtitle mb-2 text-muted">
{{ recipe.updated_at|date:"F d, Y" }}
</h6>
<a href="{% url 'recipes-detail' recipe.pk %}" class="card-link">View Recipe</a>
</div>
</div>
{% endfor %}
<!-- -->
{% endblock content %}
7) Давайте теперь создадим представления для создания, обновления и удаления представлений в файле recipes/views.py, чтобы этот файл выглядел следующим образом:
# recipes/views.py
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from . import models
class RecipeListView(ListView):
model = models.Recipe
template_name = 'recipes/home.html'
context_object_name = 'recipes'
class RecipeDetailView(DetailView):
model = models.Recipe
class RecipeDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = models.Recipe
success_url = reverse_lazy('recipes-home')
def test_func(self):
recipe = self.get_object()
return self.request.user == recipe.author
class RecipeCreateView(LoginRequiredMixin, CreateView):
model = models.Recipe
fields = ['title', 'description']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class RecipeUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = models.Recipe
fields = ['title', 'description']
def test_func(self):
recipe = self.get_object()
return self.request.user == recipe.author
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
8) Создадим шаблон для представления создания и обновления, поэтому создадим файл ‘recipes/templates/recipes/recipe_form.html’ со следующим содержанием:
# recipes/templates/recipes/recipe_form.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Add Recipe</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group py-3">
<input class="btn btn-outline-primary" type="submit" value="Save" />
</div>
</form>
<div class="border-top pt-3">
<a class="text-muted" href="{% url 'recipes-home' %}"
>Cancel</a
>
</div>
</div>
<!-- -->
{% endblock content %}
9) Нужно добавить абсолютный url в модель рецепта, чтобы django знал, куда перенаправлять после создания или обновления объекта, поэтому в recipes/model.py:
# recipes/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.
class Recipe(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def get_absolute_url(self):
return reverse("recipes-detail", kwargs={"pk": self.pk})
def __str__(self):
return self.title
10) Теперь последнее — шаблон удаления, поэтому создайте файл ‘recipes/templates/recipes/recipe_confirm_delete.html’ и добавьте эту разметку:
# recipes/templates/recipes/recipe_confirm_delete.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="mb-4">Are you sure you want to delete?</legend>
<h2>{{object}}</h2>
{{ form|crispy }}
</fieldset>
<div class="form-group py-3">
<input class="btn btn-outline-danger" type="submit" value="Delete" />
</div>
</form>
<div class="border-top pt-3">
<a class="text-muted" href="{% url 'recipes-home' %}"
>Cancel</a
>
</div>
</div>
<!-- -->
{% endblock content %}
11) Обновите пользовательский интерфейс, чтобы сделать ссылки на эти страницы, нам нужно обновить шаблон detail и base.html:
# template/recipes/recipe_detail.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipe # {{object.id}}</h1>
<div class="card my-4">
<div class="card-body">
<h5 class="card-title">{{ object.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ object.author }}</h6>
<p class="card-text">{{ object.description }}</p>
<h6 class="card-subtitle mb-2 text-muted">
{{ object.updated_at|date:"F d, Y" }}
</h6>
</div>
</div>
{% if object.author == user or user.is_staff %}
<div class="col-4">
<a class="btn btn-outline-info" href="{% url 'recipes-update' object.id %}">Update</a>
<a class="btn btn-outline-danger" href="{% url 'recipes-delete' object.id %}">Delete</a>
</div>
{% endif %}
<!-- -->
{% endblock content %}
И base.html
# templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{% if title %}
<title>Django Recipes - {{title}}</title>
{% else %}
<title>Django Recipes</title>
{% endif %}
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'recipes-home' %}">Recipes App</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a
class="nav-link"
aria-current="page"
href="{% url 'recipes-home' %}"
>Recipes</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'recipes-about' %}">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'recipes-create' %}">Add Recipe</a>
</li>
</ul>
</div>
<div class="navbar-nav">
{% if user.is_authenticated %}
<a class="nav-item nav-link" href="{% url 'user-profile' %}"
>My Profile</a
>
<a class="nav-item nav-link" href="{% url 'user-logout' %}">Logout</a>
{% else %}
<a class="nav-item nav-link" href="{% url 'user-login' %}">Login</a>
<a class="nav-item nav-link" href="{% url 'user-register' %}"
>Register</a
>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4 col-8">
{% if messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %} {% endif %} {% block content %} {% endblock %}
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"
integrity="sha384-kjU+l4N0Yf4ZOJErLsIcvOU2qSb74wXpOhqTvwVx3OElZRweTnQ6d31fXEoRD1Jy"
crossorigin="anonymous"
></script>
</body>
</html>
У вас все получилось! Теперь у вас есть приложение для приготовления сладких рецептов, которое вы можете настроить по своему вкусу или использовать для создания чего-то нового!