В этом блоге мы построим полнофункциональный сайт электронной коммерции с помощью Django и Wagtail CMS. Я также вкратце расскажу о том:
- Почему стоит выбрать фреймворк django для создания пользовательского сайта электронной коммерции.
- Какой фреймворк лучше для вашего сайта? Django CMS vs WordPress
Вы можете посмотреть демо-версию конечного продукта по следующему адресу
Почему стоит выбрать Django для электронной коммерции?
Во-первых, вот некоторые особенности Django, которые следует рассмотреть, если вы ищете подходящий фреймворк для создания магазина.
→ Масштабируемость
Django идеально подходит для стартапов в области электронной коммерции, поскольку он хорошо подходит для небольших сайтов и может отлично масштабироваться по мере роста бизнеса. Вы можете положиться на Django при одновременной работе с сотнями/тысячами посетителей. Он построен из независимых компонентов, которые вы можете отключить или заменить в зависимости от ваших потребностей в любое конкретное время.
→ Безопасность
В электронной коммерции вы хотите быть уверены, что и продавцы, и клиенты чувствуют себя в безопасности во время совершения покупок. Django предотвращает множество распространенных ошибок безопасности, которые часто ослабляют традиционные PHP CMS. Например, Django скрывает исходный код вашего сайта от прямого просмотра в интернете, динамически генерируя веб-страницы.
→ Богатый функционал
По сравнению с большинством фреймворков, Django поставляется с гораздо большим количеством функций «из коробки». Это позволяет вам сразу же создать приложение. Идеально подходит для поддержки вашего интернет-магазина с такими функциями, как аутентификация пользователей, управление контентом или RSS-лента. Если вам покажется, что чего-то не хватает, вы можете рассчитывать на сообщество Django и экосистему плагинов для расширения вашего приложения!
→ SEO-дружественный
SEO имеет первостепенное значение для любого онлайн-бизнеса. Django поддерживает лучшие практики для SEO. Человекочитаемые URL-адреса и функции sitemap обязательно понравятся любой команде маркетологов.
О, и еще, это быстро, что всегда хорошо как для удобства клиентов, так и для SEO.
→ Надежность
Django позволяет легко создавать пользовательские веб-сайты электронной коммерции. Это современный, простой в использовании веб-фреймворк Python, который способствует быстрой разработке и масштабируемой архитектуре. Его высокоуровневая абстракция облегчает создание, подключение, настройку и поддержку приложений. Кроме того, Django позволяет легко добавлять кэширование, безопасную аутентификацию и представления, не зависящие от URL.
Какая система лучше для вашего сайта электронной коммерции? Django CMS vs WordPress
API базы данных Django позволяет легко работать с базами данных. Django имеет надежную и безопасную систему аутентификации. Django имеет встроенный механизм шаблонов, что означает, что вам не нужно использовать отдельный язык шаблонов. Django — это бесплатный веб-фреймворк с открытым исходным кодом! Django также следует принципу DRY (Don’t Repeat Yourself), DRY — это принцип сокращения повторений в коде, возвращаясь к одному источнику — или «фрагменту» — многократно используемого кода, когда он вам нужен.
Django также снимает много накладных расходов. Разработчикам не нужно беспокоиться о совместимости своих программ с различными версиями Python, а значит, они могут больше времени уделять приложению.
Wagtail CMS + Snipcart e-commerce
Wagtail — это Django-система управления контентом для разработчиков. Бесплатная и с открытым исходным кодом, ее разработали добросердечные ребята из Torchbox. Она элегантна, гибка и, ИМХО, просто шикарна.
В следующем учебнике по Wagtail CMS будет отвечать за создание и управление продуктами, которые пользователи затем смогут покупать через корзину.
К концу курса вы получите надежный сайт электронной коммерции на базе Django.
Ниже приведен скриншот магазина, который мы собираемся создать:
Начнем!
Предварительные условия
- Учетная запись Snipcart.
1. Создание приложения Wagtail
Используйте pip, который поставляется вместе с Python, для установки Wagtail и его зависимостей:
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install wagtail requests
Откройте терминал и запустите новый сайт Wagtail:
$ wagtail start snipcartwagtaildemo
$ cd snipcartwagtaildemo
У нас есть еще один шаг для завершения настройки Wagtail, и это установка плагина wagtail.contrib.settings, который нам понадобится позже.
В новом проекте Wagtail откройте файл base.py, расположенный в папке snipcartwaigtaildemo/settings. Затем добавьте wagtail.contrib.settings в массив INSTALLED_APPS.
# ./setting/base.py
INSTALLED_APPS = [
...,
'wagtail.contrib.settings'
]
1.1 Создание моделей.
Первое, что вам нужно сделать, это создать модели Page. Wagtail использует эти модели Django для генерации типа страницы.
Откройте файл models.py, расположенный в домашней папке вашего продукта. Здесь вы определите все свои пользовательские модели.
Создайте две различные модели:
-
Product: определяет продукт, который вы продаете.
- ProductCustomField: определяет одно пользовательское поле продукта.
Начнем с импорта необходимых модулей:
# ./home/models.py
from django.db import models
from wagtail.core.models import Page
from modelcluster.fields import ParentalKey
from wagtail.core.models import Page, Orderable
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.contrib.settings.models import BaseSetting, register_setting
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.template.response import TemplateResponse
from django.core.paginator import Paginator
Теперь добавьте модель Product:
# ./home/models.py
class Product(Page):
def get_context(self, request):
context = super().get_context(request)
fields = []
for f in self.custom_fields.get_object_list():
if f.options:
f.options_array = f.options.split('|')
fields.append(f)
else:
fields.append(f)
context['custom_fields'] = fields
return context
sku = models.CharField(max_length=255)
short_description = models.TextField(blank=True, null=True)
price = models.DecimalField(decimal_places=2, max_digits=10)
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
content_panels = Page.content_panels + [
FieldPanel('sku'),
FieldPanel('price'),
ImageChooserPanel('image'),
FieldPanel('short_description'),
InlinePanel('custom_fields', label='Custom fields'),
]
Обновите класс homepage, который отвечает за вывод списка продуктов только на главной странице. Я буду выводить на главной странице только 6 продуктов. В любой странице Wagtail Page вы можете переопределить метод get_context. В параметрах можно добавить данные, которые будет получать представление.
class HomePage(Page):
def get_context(self, request):
context = super().get_context(request)
context['products'] = Product.objects.child_of(self).live()[:6]
return context
и ProductCustomField:
# ./home/models.py
class ProductCustomField(Orderable):
product = ParentalKey(Product, on_delete=models.CASCADE, related_name='custom_fields')
name = models.CharField(max_length=255)
options = models.CharField(max_length=500, null=True, blank=True)
panels = [
FieldPanel('name'),
FieldPanel('options')
]
Нам нужно добавить еще одно представление, которое будет отвечать за отображение всех товаров в нашем интернет-магазине, а также за постраничное отображение страницы. Я буду осуществлять постраничное отображение на стороне бэкенда, поскольку загрузка всех объектов на странице не очень эффективна. Однако мы можем кэшировать наши данные и загружать их на лицевой стороне. Это сделает пагинацию плавной, все зависит от размера вашей базы данных.
def shop(request):
products = Product.objects.live()
p = Paginator(products, 10)
#shows number of items in page
totalProducts = (p.count)
pageNum = request.GET.get('page', 1)
page1 = p.page(pageNum)
return TemplateResponse(request, 'home/shop.html', {
'products': products,
'dataSaved':page1
})
Наконец, добавьте виды разделов «О нас» и «Свяжитесь с нами», как показано ниже:
def about(request):
return TemplateResponse(request, 'home/about.html', {
})
def contact(request):
return TemplateResponse(request, 'home/contact.html', {
})
2. Добавление параметров конфигурации Snipcart
Давайте убедимся, что вы можете обновить ключ API Snipcart непосредственно из приборной панели Wagtail.
Для этого вам потребуется добавить настройки сайта.
Настройки сайта — это специальные поля, которые вы можете добавить в файл models.py. Они появятся в разделе Настройки Wagtail на приборной панели.
Импортируйте этот модуль:
# ./home/models.py
from wagtail.contrib.settings.models import BaseSetting, register_setting
Затем добавьте эти:
# ./home/models.py
@register_setting
class SnipcartSettings(BaseSetting):
api_key = models.CharField(
max_length=255,
help_text='Your Snipcart public API key'
)
3. Миграции базы данных
Теперь, когда ваши модели созданы, вам нужно сгенерировать миграции базы данных и запустить их.
В терминале выполните команду makemigrations:
$ python3 manage.py makemigrations
$ python3 manage.py migrate
Наконец, создайте своего первого пользователя CMS с помощью команды createsuperuser:
$ python3 manage.py createsuperuser
Команда попросит ввести адрес электронной почты, имя пользователя и придумать пароль. Не забудьте имя пользователя и пароль, которые вы выбрали; они понадобятся для входа в приборную панель Wagtail.
4. Создание продуктов
Запустите свой сервер следующим образом:
$ python3 manage.py runserver
Теперь откройте браузер и перейдите на сайт http://127.0.0.1:8000/admin. Используйте учетные данные, установленные ранее, для входа в систему.
Выберите главную страницу в меню Wagtail. Затем нажмите на кнопку Добавить дочернюю страницу.
Вам будет предложено выбрать тип страницы, выберите Домашняя страница и дайте ей имя.
Вернитесь в меню Главная страница и нажмите кнопку Добавить дочернюю страницу под только что созданной страницей.
Вам снова будет предложено выбрать тип страницы, на этот раз выберите Продукт.
Вы можете создать столько продуктов, сколько пожелаете.
4.1 Добавление ключа API Snipcart
Помните класс SnipcartSettings, который вы создали? Вы сможете настроить ключ API, развернув меню Настройки и перейдя к Настройкам Snipcart.
Откройте приборную панель Snipcart и получите свой открытый API-ключ (Test или Live), вернитесь в Wagtail и вставьте его в поле API-ключа.
Сохраните настройки.
Добавление новых URL-маршрутов
Я почти не использовал Wagtail и не очень хорошо знаком с тем, как обрабатываются URL, но я создал свой собственный URL для вида магазина, для отображения всех товаров:
from django.conf import settings
from django.urls import include, path
from django.contrib import admin
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
from search import views as search_views
from home import models as p
urlpatterns = [
path('django-admin/', admin.site.urls),
path('admin/', include(wagtailadmin_urls)),
path('documents/', include(wagtaildocs_urls)),
path('search/', search_views.search, name='search'),
path('shop/', p.shop, name='shop'),
path('about/', p.about, name='about'),
path('contact/', p.contact, name='contact'),
]
if settings.DEBUG:
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# Serve static and media files from development server
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns = urlpatterns + [
# For anything not caught by a more specific rule above, hand over to
# Wagtail's page serving mechanism. This should be the last pattern in
# the list:
path("", include(wagtail_urls)),
# Alternatively, if you want Wagtail pages to be served from a subpath
# of your site, rather than the site root:
# path("pages/", include(wagtail_urls)),
]
5. Создание шаблона
Теперь перейдем к созданию HTML-файлов и изменению пользовательского интерфейса нашего приложения. В качестве шаблона используется этот URL Colorlib.
Домашняя страница
На главной странице нам нужно отобразить несколько элементов из нашей базы данных. Мы уже указали 6 элементов на главной странице. Создайте 3 файла в папке home/templates/home:
- home_page.html
- продукт.html
- магазин.html
Прежде чем я покажу весь код, позвольте мне объяснить несколько важных моментов, начиная с заголовка HTML-файлов
{% load static wagtailcore_tags wagtailuserbar %}
{% load wagtailimages_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Tafari Pharmacy</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% load static wagtailsettings_tags %}
{% get_settings %}
{# Global stylesheets #}
<link href="https://fonts.googleapis.com/css?family=Rubik:400,700|Crimson+Text:400,400i" rel="stylesheet">
<link rel="stylesheet" href="{% static 'fonts/icomoon/style.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/magnific-popup.css' %}">
<link rel="stylesheet" href="{% static 'css/jquery-ui.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.carousel.min.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.theme.default.min.css' %}">
<link rel="stylesheet" href="{% static 'css/aos.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{# Snipcart #}
{% if settings.home.SnipcartSettings.api_key %}
<script async src="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js"></script>
<div hidden id="snipcart" data-api-key="{{ settings.home.SnipcartSettings.api_key }}"></div>
<link rel="stylesheet" href="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css" />
{% endif %}
</head>
У нас есть ссылка для тегов snipcart, где он определяет API ключ, который мы добавили в файл настроек, и есть ссылка для файлов snipcart как cdn файлы.
<div class="row">
{% for product in products %}
<div class="col-sm-6 col-lg-4 text-center item mb-4">
<a href="{{ product.get_url }}">
{% image product.image fill-370x270 as tmp_image %}
<img src="{{ tmp_image.url }}" alt="Image">
</a>
<h3 class="text-dark"><a href="{{ product.get_url }}">{{ product.title }}</a></h3>
<p class="price">Ksh. {{ product.price }}</p>
</div>
{% endfor %}
</div>
У нас также есть часть с продуктами, которая циклически просматривает продукты и отображает их все. Для более подробной информации вы можете посетить документацию по snip cart.
Весь код для домашней страницы выглядит следующим образом:
{% load static wagtailcore_tags wagtailuserbar %}
{% load wagtailimages_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Tafari Pharmacy</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% load static wagtailsettings_tags %}
{% get_settings %}
{# Global stylesheets #}
<link href="https://fonts.googleapis.com/css?family=Rubik:400,700|Crimson+Text:400,400i" rel="stylesheet">
<link rel="stylesheet" href="{% static 'fonts/icomoon/style.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/magnific-popup.css' %}">
<link rel="stylesheet" href="{% static 'css/jquery-ui.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.carousel.min.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.theme.default.min.css' %}">
<link rel="stylesheet" href="{% static 'css/aos.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{# Snipcart #}
{% if settings.home.SnipcartSettings.api_key %}
<script async src="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js"></script>
<div hidden id="snipcart" data-api-key="{{ settings.home.SnipcartSettings.api_key }}"></div>
<link rel="stylesheet" href="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css" />
{% endif %}
</head>
<body>
{% wagtailuserbar %}
<div class="site-wrap">
<div class="site-navbar py-2">
<div class="search-wrap">
<div class="container">
<a href="#" class="search-close js-search-close"><span class="icon-close2"></span></a>
<form action="#" method="post">
<input type="text" class="form-control" placeholder="Search keyword and hit enter...">
</form>
</div>
</div>
<div class="container">
<div class="d-flex align-items-center justify-content-between">
<div class="logo">
<div class="site-logo">
<a href="/" class="js-logo-clone">Tafari Pharmacy</a>
</div>
</div>
<div class="main-nav d-none d-lg-block">
<nav class="site-navigation text-right text-md-center" role="navigation">
<ul class="site-menu js-clone-nav d-none d-lg-block">
<li class="active"><a href="/">Home</a></li>
{% if request.path == '/' %}
<li><a href="/shop">Store</a></li>
{% endif %}
<li class="has-children">
<a href="#">Medications</a>
<ul class="dropdown">
<li><a href="#">Supplements</a></li>
<li class="has-children">
<a href="#">Vitamins</a>
<ul class="dropdown">
<li><a href="#">Supplements</a></li>
<li><a href="#">Vitamins</a></li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
<div class="icons">
<a href="#" class="icons-btn d-inline-block js-search-open"><span class="icon-search"></span></a>
<a href="" class="icons-btn d-inline-block bag">
<span class="snipcart-checkout icon-shopping-bag"></span>
<span class="snipcart-checkout number snipcart-items-count"></span>
</a>
<a href="#" class="site-menu-toggle js-menu-toggle ml-3 d-inline-block d-lg-none"><span
class="icon-menu"></span></a>
</div>
</div>
</div>
</div>
<div class="site-blocks-cover" style="background-image: url('{% static 'images/hero_1.jpg' %}');">
<div class="container">
<div class="row">
<div class="col-lg-7 mx-auto order-lg-2 align-self-center">
<div class="site-block-cover-content text-center">
<h2 class="sub-title">Effective Medicine, New Medicine Everyday</h2>
<h1>Welcome To Tafari Pharmacy</h1>
<p>
{% if request.path == '/' %}
<a href="#" class="btn btn-primary px-5 py-3">Shop Now</a>
{% endif %}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="site-section">
<div class="container">
<div class="row align-items-stretch section-overlap">
<div class="col-md-6 col-lg-4 mb-4 mb-lg-0">
<div class="banner-wrap bg-primary h-100">
<a href="#" class="h-100">
<h5>Free <br> Delivery</h5>
<p>
Amet sit amet dolor
<strong>Lorem, ipsum dolor sit amet consectetur adipisicing.</strong>
</p>
</a>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4 mb-lg-0">
<div class="banner-wrap h-100">
<a href="#" class="h-100">
<h5>Season <br> Sale 50% Off</h5>
<p>
Amet sit amet dolor
<strong>Lorem, ipsum dolor sit amet consectetur adipisicing.</strong>
</p>
</a>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4 mb-lg-0">
<div class="banner-wrap bg-warning h-100">
<a href="#" class="h-100">
<h5>Buy <br> A Gift Card</h5>
<p>
Amet sit amet dolor
<strong>Lorem, ipsum dolor sit amet consectetur adipisicing.</strong>
</p>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="site-section">
<div class="container">
<div class="row">
<div class="title-section text-center col-12">
<h2 class="text-uppercase">Popular Products</h2>
</div>
</div>
<div class="row">
{% for product in products %}
<div class="col-sm-6 col-lg-4 text-center item mb-4">
<a href="{{ product.get_url }}">
{% image product.image fill-370x270 as tmp_image %}
<img src="{{ tmp_image.url }}" alt="Image">
</a>
<h3 class="text-dark"><a href="{{ product.get_url }}">{{ product.title }}</a></h3>
<p class="price">Ksh. {{ product.price }}</p>
</div>
{% endfor %}
</div>
<div class="row mt-5">
<div class="col-12 text-center">
{% if request.path == '/' %}
<a href="/shop" class="btn btn-primary px-4 py-3">View All Products</a>
{% endif %}
</div>
</div>
</div>
</div>
<div class="site-section bg-light">
<div class="container">
<div class="row">
<div class="title-section text-center col-12">
<h2 class="text-uppercase">New Products</h2>
</div>
</div>
<div class="row">
<div class="col-md-12 block-3 products-wrap">
<div class="nonloop-block-3 owl-carousel">
<div class="text-center item mb-4">
<a href="shop-single.html"> <img src="{% static 'images/product_03.png' %}" alt="Image"></a>
<h3 class="text-dark"><a href="shop-single.html">Umcka Cold Care</a></h3>
<p class="price">$120.00</p>
</div>
<div class="text-center item mb-4">
<a href="shop-single.html"> <img src="{% static 'images/product_01.png' %}" alt="Image"></a>
<h3 class="text-dark"><a href="shop-single.html">Umcka Cold Care</a></h3>
<p class="price">$120.00</p>
</div>
<div class="text-center item mb-4">
<a href="shop-single.html"> <img src="{% static 'images/product_02.png' %}" alt="Image"></a>
<h3 class="text-dark"><a href="shop-single.html">Umcka Cold Care</a></h3>
<p class="price">$120.00</p>
</div>
<div class="text-center item mb-4">
<a href="shop-single.html"> <img src="{% static 'images/product_04.png' %}" alt="Image"></a>
<h3 class="text-dark"><a href="shop-single.html">Umcka Cold Care</a></h3>
<p class="price">$120.00</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="site-section">
<div class="container">
<div class="row">
<div class="title-section text-center col-12">
<h2 class="text-uppercase">Testimonials</h2>
</div>
</div>
<div class="row">
<div class="col-md-12 block-3 products-wrap">
<div class="nonloop-block-3 no-direction owl-carousel">
<div class="testimony">
<blockquote>
<img src="{% static 'images/person_1.jpg' %}" alt="Image" class="img-fluid w-25 mb-4 rounded-circle">
<p>“Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nemo omnis voluptatem consectetur quam tempore obcaecati maiores voluptate aspernatur iusto eveniet, placeat ab quod tenetur ducimus. Minus ratione sit quaerat unde.”</p>
</blockquote>
<p>— Kelly Holmes</p>
</div>
<div class="testimony">
<blockquote>
<img src="{% static 'images/person_2.jpg' %}" alt="Image" class="img-fluid w-25 mb-4 rounded-circle">
<p>“Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nemo omnis voluptatem consectetur quam tempore
obcaecati maiores voluptate aspernatur iusto eveniet, placeat ab quod tenetur ducimus. Minus ratione sit quaerat
unde.”</p>
</blockquote>
<p>— Rebecca Morando</p>
</div>
<div class="testimony">
<blockquote>
<img src="{% static 'images/person_3.jpg' %}" alt="Image" class="img-fluid w-25 mb-4 rounded-circle">
<p>“Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nemo omnis voluptatem consectetur quam tempore
obcaecati maiores voluptate aspernatur iusto eveniet, placeat ab quod tenetur ducimus. Minus ratione sit quaerat
unde.”</p>
</blockquote>
<p>— Lucas Gallone</p>
</div>
<div class="testimony">
<blockquote>
<img src="{% static 'images/person_4.jpg' %}" alt="Image" class="img-fluid w-25 mb-4 rounded-circle">
<p>“Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nemo omnis voluptatem consectetur quam tempore
obcaecati maiores voluptate aspernatur iusto eveniet, placeat ab quod tenetur ducimus. Minus ratione sit quaerat
unde.”</p>
</blockquote>
<p>— Andrew Neel</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="site-section bg-secondary bg-image" style="background-image: url('{% static 'images/bg_2.jpg' %}');">
<div class="container">
<div class="row align-items-stretch">
<div class="col-lg-6 mb-5 mb-lg-0">
<a href="#" class="banner-1 h-100 d-flex" style="background-image: url('{% static 'images/bg_1.jpg' %}');">
<div class="banner-1-inner align-self-center">
<h2>Tafari Pharmacy Products</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Molestiae ex ad minus rem odio voluptatem.
</p>
</div>
</a>
</div>
<div class="col-lg-6 mb-5 mb-lg-0">
<a href="#" class="banner-1 h-100 d-flex" style="background-image: url('{% static 'images/bg_2.jpg' %}');">
<div class="banner-1-inner ml-auto align-self-center">
<h2>Rated by Experts</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Molestiae ex ad minus rem odio voluptatem.
</p>
</div>
</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="container">
<div class="row">
<div class="col-md-6 col-lg-3 mb-4 mb-lg-0">
<div class="block-7">
<h3 class="footer-heading mb-4">About Us</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius quae reiciendis distinctio voluptates
sed dolorum excepturi iure eaque, aut unde.</p>
</div>
</div>
<div class="col-lg-3 mx-auto mb-5 mb-lg-0">
<h3 class="footer-heading mb-4">Quick Links</h3>
<ul class="list-unstyled">
<li><a href="#">Supplements</a></li>
<li><a href="#">Vitamins</a></li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</div>
<div class="col-md-6 col-lg-3">
<div class="block-5 mb-5">
<h3 class="footer-heading mb-4">Contact Info</h3>
<ul class="list-unstyled">
<li class="address">203 Fake St. Mountain View, San Francisco, California, USA</li>
<li class="phone"><a href="tel://23923929210">+2 392 3929 210</a></li>
<li class="email">emailaddress@domain.com</li>
</ul>
</div>
</div>
</div>
<div class="row pt-5 mt-5 text-center">
<div class="col-md-12">
<p>
<!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. -->
Copyright ©
<script>document.write(new Date().getFullYear());</script> All rights reserved | This template is made
with <i class="icon-heart" aria-hidden="true"></i> by <a href="https://colorlib.com" target="_blank"
class="text-primary">Colorlib</a>
<!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. -->
</p>
</div>
</div>
</div>
</footer>
</div>
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'js/jquery-ui.js' %}"></script>
<script src="{% static 'js/popper.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/owl.carousel.min.js' %}"></script>
<script src="{% static 'js/jquery.magnific-popup.min.js' %}"></script>
<script src="{% static 'js/aos.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
Страница всех товаров
В файле product.html добавьте строки кода, приведенные ниже. Я не использовал встроенную функциональность шаблонов django для работы в компонентах, поэтому нам придется повторять теги, которые мы добавили в файл домашней страницы, для каждой создаваемой страницы. Скопируйте и вставьте приведенный ниже код в файл templates/home/shop.html:
{% load static wagtailcore_tags wagtailuserbar %}
{% load wagtailimages_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Tafari Tafari Pharmacycy — Colorlib Template</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% load static wagtailsettings_tags %}
{% get_settings %}
{# Global stylesheets #}
<link href="https://fonts.googleapis.com/css?family=Rubik:400,700|Crimson+Text:400,400i" rel="stylesheet">
<link rel="stylesheet" href="{% static 'fonts/icomoon/style.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/magnific-popup.css' %}">
<link rel="stylesheet" href="{% static 'css/jquery-ui.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.carousel.min.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.theme.default.min.css' %}">
<link rel="stylesheet" href="{% static 'css/aos.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{# Snipcart #}
{% if settings.home.SnipcartSettings.api_key %}
<script async src="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js"></script>
<div hidden id="snipcart" data-api-key="{{ settings.home.SnipcartSettings.api_key }}"></div>
<link rel="stylesheet" href="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css" />
{% endif %}
</head>
<body>
{% wagtailuserbar %}
<div class="site-wrap">
<div class="site-navbar py-2">
<div class="search-wrap">
<div class="container">
<a href="#" class="search-close js-search-close"><span class="icon-close2"></span></a>
<form action="#" method="post">
<input type="text" class="form-control" placeholder="Search keyword and hit enter...">
</form>
</div>
</div>
<div class="container">
<div class="d-flex align-items-center justify-content-between">
<div class="logo">
<div class="site-logo">
<a href="/" class="js-logo-clone">Tafari Pharmacy</a>
</div>
</div>
<div class="main-nav d-none d-lg-block">
<nav class="site-navigation text-right text-md-center" role="navigation">
<ul class="site-menu js-clone-nav d-none d-lg-block">
<li><a href="/">Home</a></li>
<li class="active"><a href="/shop">Store</a></li>
<li class="has-children">
<a href="#">Medications</a>
<ul class="dropdown">
<li><a href="#">Supplements</a></li>
<li class="has-children">
<a href="#">Vitamins</a>
<ul class="dropdown">
<li><a href="#">Supplements</a></li>
<li><a href="#">Vitamins</a></li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
<div class="icons">
<a href="#" class="icons-btn d-inline-block js-search-open"><span class="icon-search"></span></a>
<a href="" class="icons-btn d-inline-block bag">
<span class="snipcart-checkout icon-shopping-bag"></span>
<span class="snipcart-checkout number snipcart-items-count"></span>
</a>
<a href="#" class="site-menu-toggle js-menu-toggle ml-3 d-inline-block d-lg-none"><span
class="icon-menu"></span></a>
</div>
</div>
</div>
</div>
<div class="bg-light py-3">
<div class="container">
<div class="row">
<div class="col-md-12 mb-0"><a href="/">Home</a> <span class="mx-2 mb-0">/</span> <strong class="text-black">Store</strong></div>
</div>
</div>
</div>
<div class="site-section">
<div class="container">
{% for product in dataSaved.object_list %}
<div class="row">
<div class="col-sm-6 col-lg-4 text-center item mb-4">
<a href="{{ product.get_url }}">
{% image product.image fill-370x270 as tmp_image %}
<img src="{{ tmp_image.url }}" alt="Image">
</a>
<h3 class="text-dark"><a href="{{ product.get_url }}">{{ product.title }}</a></h3>
<p class="price">Ksh. {{ product.price }}</p>
</div>
</div>
{% endfor %}
<div class="row mt-5">
<div class="col-md-12 text-center">
<div class="site-block-27">
{% if dataSaved.has_other_pages %}
<ul class="pagination justify-content-center">
{% if dataSaved.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ dataSaved.previous_page_number }}" tabindex="-1">Previous</a>
</li>
{% endif %}
{% for i in dataSaved.paginator.page_range %}
{% if dataSaved.number == i %}
<li class="page-item active">
<a class="page-link" href="#">{{ i }}<span class="sr-only">(current)</span></a>
</li>
{% endif %}
{% endfor %}
<li class="page-item">
{% if dataSaved.has_next %}
<a class="page-link" href="?page={{ dataSaved.next_page_number }}">Next</a>
{% endif %}
</li>
</ul>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="site-section bg-secondary bg-image" style="background-image: url('{% static 'images/bg_2.jpg' %}');">
<div class="container">
<div class="row align-items-stretch">
<div class="col-lg-6 mb-5 mb-lg-0">
<a href="#" class="banner-1 h-100 d-flex" style="background-image: url('{% static 'images/bg_1.jpg' %}');">
<div class="banner-1-inner align-self-center">
<h2>Tafari Pharmacy Products</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Molestiae ex ad minus rem odio voluptatem.
</p>
</div>
</a>
</div>
<div class="col-lg-6 mb-5 mb-lg-0">
<a href="#" class="banner-1 h-100 d-flex" style="background-image: url('{% static 'images/bg_2.jpg' %}');">
<div class="banner-1-inner ml-auto align-self-center">
<h2>Rated by Experts</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Molestiae ex ad minus rem odio voluptatem.
</p>
</div>
</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="container">
<div class="row">
<div class="col-md-6 col-lg-3 mb-4 mb-lg-0">
<div class="block-7">
<h3 class="footer-heading mb-4">About Us</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius quae reiciendis distinctio voluptates
sed dolorum excepturi iure eaque, aut unde.</p>
</div>
</div>
<div class="col-lg-3 mx-auto mb-5 mb-lg-0">
<h3 class="footer-heading mb-4">Quick Links</h3>
<ul class="list-unstyled">
<li><a href="#">Supplements</a></li>
<li><a href="#">Vitamins</a></li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</div>
<div class="col-md-6 col-lg-3">
<div class="block-5 mb-5">
<h3 class="footer-heading mb-4">Contact Info</h3>
<ul class="list-unstyled">
<li class="address">203 Fake St. Mountain View, San Francisco, California, USA</li>
<li class="phone"><a href="tel://23923929210">+2 392 3929 210</a></li>
<li class="email">emailaddress@domain.com</li>
</ul>
</div>
</div>
</div>
<div class="row pt-5 mt-5 text-center">
<div class="col-md-12">
<p>
<!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. -->
Copyright ©
<script>document.write(new Date().getFullYear());</script> All rights reserved | This template is made
with <i class="icon-heart" aria-hidden="true"></i> by <a href="https://colorlib.com" target="_blank"
class="text-primary">Colorlib</a>
<!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. -->
</p>
</div>
</div>
</div>
</footer>
</div>
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'js/jquery-ui.js' %}"></script>
<script src="{% static 'js/popper.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/owl.carousel.min.js' %}"></script>
<script src="{% static 'js/jquery.magnific-popup.min.js' %}"></script>
<script src="{% static 'js/aos.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.custom-field-select').onchange = function(event) {
if (event.target.dataset.field) {
document.querySelector('.snipcart-add-item')
.dataset['itemCustom' + event.target.dataset.field + 'Value'] = event.target.value;
}
};
},false);
</script>
</body>
</html>
Код такой же, как и код главной страницы, с той лишь разницей, что в файле shop.html мы разбиваем страницу на страницы и отображаем все товары, а не только 6, как на главной странице.
Информация о товарах и страница отображения корзины
На странице товаров нам нужно отобразить товар, когда пользователь нажимает на него, например, так:
Для этого нам нужно создать шаблон, который будет связан с моделью страницы Product. В файле product.html в папке home/templates/home добавьте:
{% load static wagtailcore_tags wagtailuserbar %}
{% load wagtailimages_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Tafari Pharmacy — Colorlib Template</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% load static wagtailsettings_tags %}
{% get_settings %}
{# Global stylesheets #}
<link href="https://fonts.googleapis.com/css?family=Rubik:400,700|Crimson+Text:400,400i" rel="stylesheet">
<link rel="stylesheet" href="{% static 'fonts/icomoon/style.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/magnific-popup.css' %}">
<link rel="stylesheet" href="{% static 'css/jquery-ui.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.carousel.min.css' %}">
<link rel="stylesheet" href="{% static 'css/owl.theme.default.min.css' %}">
<link rel="stylesheet" href="{% static 'css/aos.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{# Snipcart #}
{% if settings.home.SnipcartSettings.api_key %}
<script async src="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js"></script>
<div hidden id="snipcart" data-api-key="{{ settings.home.SnipcartSettings.api_key }}"></div>
<link rel="stylesheet" href="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css" />
{% endif %}
</head>
<body>
{% wagtailuserbar %}
<div class="site-wrap">
<div class="site-navbar py-2">
<div class="search-wrap">
<div class="container">
<a href="#" class="search-close js-search-close"><span class="icon-close2"></span></a>
<form action="#" method="post">
<input type="text" class="form-control" placeholder="Search keyword and hit enter...">
</form>
</div>
</div>
<div class="container">
<div class="d-flex align-items-center justify-content-between">
<div class="logo">
<div class="site-logo">
<a href="/" class="js-logo-clone">Tafari Pharmacy</a>
</div>
</div>
<div class="main-nav d-none d-lg-block">
<nav class="site-navigation text-right text-md-center" role="navigation">
<ul class="site-menu js-clone-nav d-none d-lg-block">
<li><a href="/">Home</a></li>
<li class="active"><a href="/shop">Store</a></li>
<li class="has-children">
<a href="#">Medications</a>
<ul class="dropdown">
<li><a href="#">Supplements</a></li>
<li class="has-children">
<a href="#">Vitamins</a>
<ul class="dropdown">
<li><a href="#">Supplements</a></li>
<li><a href="#">Vitamins</a></li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
<div class="icons">
<a href="#" class="icons-btn d-inline-block js-search-open"><span class="icon-search"></span></a>
<a href="" class="icons-btn d-inline-block bag">
<span class="snipcart-checkout icon-shopping-bag"></span>
<span class="snipcart-checkout number snipcart-items-count"></span>
</a>
<a href="#" class="site-menu-toggle js-menu-toggle ml-3 d-inline-block d-lg-none"><span
class="icon-menu"></span></a>
</div>
</div>
</div>
</div>
<div class="bg-light py-3">
<div class="container">
<div class="row">
<div class="col-md-12 mb-0"><a href="/">Home</a> <span class="mx-2 mb-0">/</span> <a
href="/shop">Store</a> <span class="mx-2 mb-0">/</span> <strong class="text-black">{{ page.title }}</strong></div>
</div>
</div>
</div>
<div class="site-section">
<div class="container">
<div class="row">
<div class="col-md-5 mr-auto">
<div class="border text-center">
{% image page.image max-370x270 as temp_image %}
<img src="{{ temp_image.url }}" alt="Image" class="img-fluid p-5">
</div>
</div>
<div class="col-md-6">
<h2 class="text-black">{{ page.title }}</h2>
<p>{{page.short_description}}</p>
<p><del>$95.00</del> <strong class="text-primary h4">Ksh. {{ page.price }}</strong></p>
<p>
{% for f in custom_fields %}
{% if f.options_array|length > 0 %}
<div class="form-group">
<label class="form-label" for="{{ f.name|lower }}">
{{ f.name }}:
</label>
<select class="form-select custom-field-select" id="{{ f.name|lower }}" data-field="{{ forloop.counter }}">
{% for opt in f.options_array %}
<option>
{{ opt }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
{% endfor %}
</p>
<button class="snipcart-add-item btn btn-primary mt-6 py-2 px-4 bg-gradient-to-r from-green-400 to-blue-500 hover:from-pink-500 hover:to-yellow-500 text-white font-bold rounded-full shadow-offset hover:shadow-lg transition duration-300"
data-item-name="{{ page.title }}"
data-item-id="{{ page.sku }}"
data-item-url="{{ page.get_full_url }}"
data-item-price="{{ page.price }}"
data-item-description="{{ page.short_description}}"
data-item-image="{{ temp_image.url }}"
{% for f in custom_fields %}
data-item-custom{{forloop.counter}}-name="{{f.name}}"
data-item-custom{{forloop.counter}}-options="{{f.options}}"
{% endfor %}>Add to cart
</button>
<div class="mt-5">
<ul class="nav nav-pills mb-3 custom-pill" id="pills-tab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="pills-home-tab" data-toggle="pill" href="#pills-home" role="tab"
aria-controls="pills-home" aria-selected="true">Ordering Information</a>
</li>
<li class="nav-item">
<a class="nav-link" id="pills-profile-tab" data-toggle="pill" href="#pills-profile" role="tab"
aria-controls="pills-profile" aria-selected="false">Specifications</a>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab">
<table class="table custom-table">
<thead>
<th>Material</th>
<th>Description</th>
<th>Packaging</th>
</thead>
<tbody>
<tr>
<th scope="row">OTC022401</th>
<td>Pain Management: Acetaminophen PM Extra-Strength Caplets, 500 mg, 100/Bottle</td>
<td>1 BT</td>
</tr>
<tr>
<th scope="row">OTC022401</th>
<td>Pain Management: Acetaminophen PM Extra-Strength Caplets, 500 mg, 100/Bottle</td>
<td>144/CS</td>
</tr>
<tr>
<th scope="row">OTC022401</th>
<td>Pain Management: Acetaminophen PM Extra-Strength Caplets, 500 mg, 100/Bottle</td>
<td>1 EA</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="pills-profile" role="tabpanel" aria-labelledby="pills-profile-tab">
<table class="table custom-table">
<tbody>
<tr>
<td>HPIS CODE</td>
<td class="bg-light">999_200_40_0</td>
</tr>
<tr>
<td>HEALTHCARE PROVIDERS ONLY</td>
<td class="bg-light">No</td>
</tr>
<tr>
<td>LATEX FREE</td>
<td class="bg-light">Yes, No</td>
</tr>
<tr>
<td>MEDICATION ROUTE</td>
<td class="bg-light">Topical</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="site-section bg-secondary bg-image" style="background-image: url('images/bg_2.jpg');">
<div class="container">
<div class="row align-items-stretch">
<div class="col-lg-6 mb-5 mb-lg-0">
<a href="#" class="banner-1 h-100 d-flex" style="background-image: url('images/bg_1.jpg');">
<div class="banner-1-inner align-self-center">
<h2>Tafari Pharmacy Products</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Molestiae ex ad minus rem odio voluptatem.
</p>
</div>
</a>
</div>
<div class="col-lg-6 mb-5 mb-lg-0">
<a href="#" class="banner-1 h-100 d-flex" style="background-image: url('images/bg_2.jpg');">
<div class="banner-1-inner ml-auto align-self-center">
<h2>Rated by Experts</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Molestiae ex ad minus rem odio voluptatem.
</p>
</div>
</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="container">
<div class="row">
<div class="col-md-6 col-lg-3 mb-4 mb-lg-0">
<div class="block-7">
<h3 class="footer-heading mb-4">About Us</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius quae reiciendis distinctio voluptates
sed dolorum excepturi iure eaque, aut unde.</p>
</div>
</div>
<div class="col-lg-3 mx-auto mb-5 mb-lg-0">
<h3 class="footer-heading mb-4">Quick Links</h3>
<ul class="list-unstyled">
<li><a href="#">Supplements</a></li>
<li><a href="#">Vitamins</a></li>
<li><a href="#">Diet & Nutrition</a></li>
<li><a href="#">Tea & Coffee</a></li>
</ul>
</div>
<div class="col-md-6 col-lg-3">
<div class="block-5 mb-5">
<h3 class="footer-heading mb-4">Contact Info</h3>
<ul class="list-unstyled">
<li class="address">203 Fake St. Mountain View, San Francisco, California, USA</li>
<li class="phone"><a href="tel://23923929210">+2 392 3929 210</a></li>
<li class="email">emailaddress@domain.com</li>
</ul>
</div>
</div>
</div>
<div class="row pt-5 mt-5 text-center">
<div class="col-md-12">
<p>
<!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. -->
Copyright ©
<script>document.write(new Date().getFullYear());</script> All rights reserved | This template is made
with <i class="icon-heart" aria-hidden="true"></i> by <a href="https://colorlib.com" target="_blank"
class="text-primary">Colorlib</a>
<!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. -->
</p>
</div>
</div>
</div>
</footer>
</div>
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'js/jquery-ui.js' %}"></script>
<script src="{% static 'js/popper.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/owl.carousel.min.js' %}"></script>
<script src="{% static 'js/jquery.magnific-popup.min.js' %}"></script>
<script src="{% static 'js/aos.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.custom-field-select').onchange = function(event) {
if (event.target.dataset.field) {
document.querySelector('.snipcart-add-item')
.dataset['itemCustom' + event.target.dataset.field + 'Value'] = event.target.value;
}
};
},false);
</script>
</body>
</html>
Включенный выше javascript отвечает за обновление кнопки покупки Snipcart при выборе пользовательского поля на странице. Этот код обновляет атрибуты данных кнопки при изменении значения выбора.
Живая демонстрация
Посмотреть демонстрацию в реальном времени
Заключительные мысли о проекте
Wagtail прост, интуитивно понятен и минимален. Я думаю, что Wagtail — это отличная безголовая CMS, и мне понравилось мое небольшое путешествие с Django, Wagtail CMS и Bootstrap. Дайте мне знать, что вы думаете о Wagtail в разделе комментариев!