Создание приложения для рецептов на Django – учебное пособие

В этом уроке мы создадим приложение для рецептов на Django, где пользователи смогут зарегистрироваться и создавать, читать, обновлять и удалять рецепты. Ниже я также приведу ссылки на видео 📽️, связанные с каждым шагом.

Содержание
  1. Конечный продукт:
  2. Шаг 1: Настройка проекта
  3. 🍾🎉 Ух ты, наш проект Django запущен и работает!
  4. Шаг 2: Приложения, URL и представления Django
  5. Запустите сервер снова, зайдите на localhost:8000 и вы должны увидеть ‘Welcome to the Recipes home page’ 🚀.
  6. Шаг 3: HTML шаблоны + пользовательский интерфейс
  7. Теперь вернитесь на главную страницу (запустите сервер!) и вы должны увидеть отображение данных рецептов 🍕🍟🍔.
  8. Шаг 4: Стиль Bootstrap
  9. Теперь у нас есть навигация, и она выглядит немного лучше 🦋
  10. Шаг 5: Настройка администратора Django
  11. Админка django – это удобный инструмент при разработке и даже в реальной жизни для добавления/редактирования/обновления/удаления данных.
  12. Шаг 6: Создание модели базы данных
  13. Удивительно – теперь у нас есть реальные данные, которые хранятся и отображаются на нашем сайте!
  14. Шаг 7: Регистрация пользователей 📝
  15. Теперь новые пользователи могут зарегистрироваться!
  16. Шаг 8: Вход и выход
  17. Потрясающе! Теперь наше приложение имеет полную авторизацию пользователей, пользователи могут регистрироваться, входить и выходить!
  18. Шаг 9: Рецепты CRUD:
  19. У вас все получилось! Теперь у вас есть приложение для приготовления сладких рецептов, которое вы можете настроить по своему вкусу или использовать для создания чего-то нового!

Конечный продукт:


Шаг 1: Настройка проекта

К концу этого шага у нас будет запущен проект Django. Мы будем использовать python 3.8. Все, что выше 3.3, должно работать.

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

    Mac:

  2. Далее нам нужно установить Django, в терминале введите:

  3. Создайте проект Django. Каждый проект Django может состоять из 1 или более приложений, в вашем терминале (символ ‘.’ создает в текущем каталоге):

  4. Запустите сервер и просмотрите 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)
Enter fullscreen mode Выйти из полноэкранного режима

Теперь вернитесь на главную страницу (запустите сервер!) и вы должны увидеть отображение данных рецептов 🍕🍟🍔.


Шаг 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)
Enter fullscreen mode Выйти из полноэкранного режима

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>
Войти в полноэкранный режим Выйти из полноэкранного режима

У вас все получилось! Теперь у вас есть приложение для приготовления сладких рецептов, которое вы можете настроить по своему вкусу или использовать для создания чего-то нового!

Оцените статью
Procodings.ru
Добавить комментарий