View
522
Download
8
Category
Preview:
Citation preview
Сергей Бурма
#kranonit e20
Обо мне
● использую Python в своих корыстных и не
очень целях уже пятый год;
● поучаствовал в нескольких больших
незапустившихся проектах;
● приложил руку к сайтам wokifood.ru,
dewote.com, biz-translate.com;
● изредка пишу на хабр (ник batment).
О чем вы сегодня узнаете
● немного о Python;
● много о фреймворке Django;
● о том как сделать очень простой
интернет-магазин с админкой.
Особенности Python
● приоритет на читаемость;
● данные бывают изменяемые и
неизменяемые (mutable, immutable)
● жесткие стандарты оформления кода;
● любая сущность - это объект;
● очень своеобразное ООП;
● магические методы.
Дисклеймер
● мы используем Python 3;
● код представлен максимально наглядно,
но в нем скорее всего есть неувязки и
ошибки, будьте осторожны.
Почему Django?
● очень популярен, активно развивается;
● покрывает большинство нужд веб-
разработчиков;
● минимизирует рутину;
● достаточно приятен в обращении.
Составляющие Django-приложения
● основная парадигма - Models Views
Templates;
● модели;
● формы;
● представления;
● шаблоны;
● маршрутизаторы.
Недостатки Django
● однопоточный и как следствие тормозной;
● нестандартные вещи делать сложно (но
зато более-менее красиво);
● имеет жесткую структуру и идеологию,
что в редких случаях может помешать
разработке.
Что понадобится
● настроенная машина с Linux (лучше всего
Ubuntu, подойдет виртуальная) или
MacOS;
● редактор Python и HTML. Для начала
рекомендую PyCharm Professional Edition.
● или вместо всего этого онлайн-среда,
например c9.io
Подготовка системы
sudo apt-get install build-essential python3-dev python3 python3-virtualenv python3-
pip zlib1g-dev libpng12-dev libjpeg-dev git
virtualenv -p /usr/bin/python3 venv
source venv/bin/activate
git clone https://github.com/batment/kranonit-shop
cd kranonit-shop
pip install -r requirements.txt
Инициализация проекта
cd ..
django-admin startproject shop
cd shop
python manage.py startapp catalog
python manage.py startapp cart
python manage.py migrate
python manage.py syncdb
python manage.py runserver
Структура интернет-магазина
Настройки Django-проекта
● параметры подключения к базам данных;
● расположение директорий со
статическими файлами (JS, CSS, etc.) и
загружаемыми файлами;
● локализация, интернационализация и т.д.;
● подключенные приложения;
● параметры безопасности.
Настройки проекта ч.1
import dj_database_url
DEFAULT_DB = 'sqlite:///' + BASE_DIR + '/db.sqlite3'
DATABASES = {
'default': dj_database_url.config(default=DEFAULT_DB)
}
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates'),
)
STATICFILES_DIRS = (
os.path.join(BASE_DIR, ‘static’),
)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Настройки проекта ч.2
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'catalog',
'cart',
)
SECRET_KEY = '0!+6_x3g49_do4d5)e=)07ke%wza^1)&@*=@!aw1x&%&kv24j3'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
ROOT_URLCONF = 'shop.urls'
WSGI_APPLICATION = 'shop.wsgi.application'
Описание модели в Django
● класс, наследуемый от models.Model;
● нужные поля с данными описываются как
поля класса;
● класс Meta, объявленный внутри модели
содержит ее настройки;
● методы __str__ или __unicode__ упростят
текстовый вывод модели.
Модель ProductCategory
class ProductCategory(models.Model):
name = models.CharField(max_length=128)
class Meta:
verbose_name = 'Category'
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
Базовые типы полей
● CharField - простое текстовое поле
ограниченной длинны;
● IntegerField - для хранения целых чисел;
● BooleanField - для хранения логических
данных (True, False)
● ForeignKey - для связи с другой моделью;
● ManyToManyField - для связи со многими
моделями.
Загружаемые файлы
● в базовой поставке FileField и
ImageFileField;
● в базе хранится только имя файла;
● файл хранится в MEDIA_ROOT;
● файлы нужно удалять вручную;
● в поле нужно указать upload_to;
● для картинок рекомендую easy_thumbnails
Модель Product
class Product(models.Model):
name = models.CharField(max_length=128)
description = models.TextField(max_length=10000, blank=True, null=True)
price = models.DecimalField(max_digits=8, decimal_places=2)
category = models.ForeignKey('catalog.ProductCategory')
image = models.ImageField(upload_to='products')
class Meta:
verbose_name = 'Product'
verbose_name_plural = 'Products'
def __str__(self):
return self.name
Модель Order
class Order(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Opened at')
closed_at = models.DateTimeField(verbose_name='Closed at', blank=True, null=True)
is_closed = models.BooleanField(default=False, verbose_name='Is closed')
is_processed = models.BooleanField(default=False, verbose_name='Is processed')
phone = models.CharField(max_length=32, null=True)
address = models.CharField(max_length=256, null=True)
name = models.CharField(max_length=64, verbose_name='Contact name', null=True)
class Meta:
verbose_name = 'Order'
verbose_name_plural = 'Orders'
def price(self):
total_price = 0
for p in self.positions.all():
total_price += p.price()
return total_price
Модель OrderPosition
class OrderPosition(models.Model):
order = models.ForeignKey('cart.Order', related_name='positions')
product = models.ForeignKey('catalog.Product')
count = models.PositiveIntegerField(default=0)
class Meta:
verbose_name = 'Order position'
verbose_name_plural = 'Order positions'
ordering = ['-count']
def price(self):
return self.product.price * self.count
Формы в Django
● класс, наследуемый от forms.Form;
● описывается аналогично моделям;
● в описании полей можно указать
необходимый контрол (widget);
● могут генерировать готовый HTML-код.
Формы для моделей (ModelForm)
● в параметрах нужно только указать
модель, форма сгенерируется
автоматически;
● можно также указать включенные поля и
их порядок вывода;
● вместо этого можно указать исключенные
поля.
Набор форм (FormSet)
● предназначен для ситуаций, когда нужно
редактировать сразу много однотипных
данных;
● генерируется при помощи специальных
функций-фабрик;
● формы-составляющие являются
обычными формами.
Форма для добавления в корзину
class AddToCartForm(forms.Form):
product = forms.ModelChoiceField(
Product.objects.all(),
widget=forms.HiddenInput
)
Форма для оформления заказа
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ['name', 'address', 'phone']
Форма для позиций заказа
class OrderPositionForm(forms.ModelForm):
class Meta:
model = OrderPosition
fields = ['product', 'count']
OrderPositionFormset = forms.inlineformset_factory(
Order,
OrderPosition,
OrderPositionForm,
extra=0,
)
Представления в Django
● берут данные из GET или POST-массива,
моделей и форм, передают в шаблон;
● могут быть функцией - минимум
параметров и полная свобода в
функционале или классом на основе
базовых представлений, с настройкой
готового кода.
● get_context_data - формирует данные для
вывода в шаблон, можно добавить туда
свои;
● get_queryset - отправляет запрос в базу,
можно изменить любым образом;
● get_template_name - определяет шаблон,
можно подставлять другой при, например,
ajax-запросе.
Функционал Class Based Views
ListView
● служит для вывода списка моделей;
● поддерживает разбитие на страницы
(pagination).
DetailView
● служит для вывода одной модели.
Список товаров ч.1
class ProductList(ShopMixin, ListView):
model = Product
template_name = 'product_list.html'
context_object_name = 'products'
def __init__(self, *args, **kwargs):
self.category = None
super().__init__(*args, **kwargs)
Список товаров ч.2
def get_category(self):
category_id = self.kwargs.get('category_id')
category = None
if category_id:
category = get_object_or_404(
ProductCategory,
id=category_id,
)
self.category = category
return category
def get_queryset(self):
queryset = super().get_queryset()
if self.category:
queryset = queryset.filter(
category=self.category,
)
return queryset
Просмотр товара
class ProductDetailed(ShopMixin, DetailView):
model = Product
template_name = 'product.html'
context_object_name = 'product'
Примесь ShopMixin
class ShopMixin(object):
"""Adds categories and current order to render context"""
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
categories = ProductCategory.objects.all()
data['categories'] = categories
data['order'] = get_order(self.request)
return data
Представление на базе функции
● принимает объект запроса и параметры
URL;
● нужно все делать вручную;
● позволяет вызывать любые побочные
эффекты;
● более явный и понятный код.
Представление add_to_cart
def add_to_cart(request):
order = get_order(request)
if not order:
order = Order.objects.create()
request.session['order_id'] = order.id
form = AddToCartForm(request.POST)
if form.is_valid():
product = form.cleaned_data['product']
order_position, created = OrderPosition.objects.get_or_create(
product=product,
order=order,
)
order_position.count += 1
order_position.save()
return redirect('cart')
● обычный словарь в request.session;
● у каждого пользователя свой на основе
cookie session_id;
● может хранится в базе, в redis или в
cookies (не рекомендуется);
● может быть в Pickles или JSON;
● хранить лучше не модели, а их id.
Сессии в Django
Функция get_order
def get_order(request):
order_id = request.session.get('order_id')
order = None
if order_id:
try:
order = Order.objects.get(id=order_id)
except Order.DoesNotExist:
pass
return order
CreateView
UpdateView
● для редактирования моделей;
● обязательно принимает pk или slug.
● для создания моделей;
● может сам генерировать форму;
● содержит функции form_valid, form_invalid.
Редактирование заказа ч.1
class OrderDetails(UpdateView):
model = Order
template_name = 'cart.html'
form_class = OrderForm
success_url = '/finish'
def get_object(self, queryset=None):
return get_order(self.request)
def get_formset(self):
return OrderPositionFormset(**self.get_form_kwargs())
Редактирование заказа ч.2
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['formset'] = self.get_formset()
return data
def form_valid(self, form):
formset = self.get_formset()
if formset.is_valid():
for position_form in formset:
position_form.save()
return super().form_valid(form)
else:
return self.form_invalid(form)
Завершение заказа
def close_order(request):
if request.POST:
order = get_order(request)
order.is_closed = True
order.closed_at = datetime.datetime.now()
order.save()
return render(request, 'close_order.html', {
'order': order,
})
else:
return redirect('order')
TemplateView
class AboutUs(TemplateView):
template_name = 'about_us.html'
Страница About us
● для вывода статичной или частично статичной страницы без моделей.
Админ-сайт в Django
● небольшая встроенная CMS, которой вам
скорее всего хватит;
● позволяет настроить очень многое, но не
все;
● настраивается для каждой модели
отдельно, плюс можно по связям сделать
встроенную админку.
Настройка админки каталога
from django.contrib import admin
from catalog.models import Product, ProductCategory
admin.site.register(Product)
admin.site.register(ProductCategory)
Настройка админки заказов
class OrderPositionInline(admin.TabularInline):
model = OrderPosition
class OrderAdmin(admin.ModelAdmin):
inlines = [
OrderPositionInline,
]
readonly_fields = ['created_at', 'closed_at']
admin.site.register(Order, OrderAdmin)
Маршрутизация в Django● ROOT_URLCONF со списком url_patterns;
● каждый элемент - это regexp с
соответствующим представлением.
● CBV должны вызвать метод as_view для
вставки в список;
● для представлений-функций достаточно
указать ссылку ‘app.views.view’;
● можно включать другие файлы c
маршрутами через include
Маршруты приложения ч.1
from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from catalog.views import ProductDetailed, ProductList, AboutUs
from cart.views import OrderDetails
urlpatterns = patterns(
'',
url(r'^(?P<category_id>\d+)?$', ProductList.as_view(), name='products'),
url(r'^product/(?P<pk>\d+)/$', ProductDetailed.as_view(), name='product'),
url(r'^add/$', 'cart.views.add_to_cart', name='add_to_cart'),
url(r'^cart/$', OrderDetails.as_view(), name='cart'),
url(r'^finish/$', 'cart.views.close_order', name='finish'),
url(r'^about/$', AboutUs.as_view(), name='about'),
url(r'^admin/', include(admin.site.urls)),
)
Маршруты приложения ч.2
urlpatterns += [static(
settings.STATIC_URL
) + static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)]
Язык шаблонов в Django
● очень простой;
● состоит из тегов и фильтров;
● теги нужны для вывода контента;
● фильтры нужны для обработки
переменных или результатов работы
других фильтров;
● исключения по возможности
подавляются.
Главный шаблон
● в основе лежит скелет getskeleton.com
● для встраивания используется тег {% include ‘filename’ %}
● для наследования используется тег {% extends ‘filename’ %}
● тег extends должен быть на первой
строке файла.
Код главного шаблона<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/static/css/normalize.css">
<link rel="stylesheet" href="/static/css/skeleton.css">
</head>
<body>
<div class="container">
{% include ’header.html’ %}
{% block body %}
<div class="row">
<div class="twelve columns">
{% block content %}{% endblock %}
</div>
</div>
{% endblock %}
</div>
</body>
</html>
Используемые теги
● url для вставки ссылок;
● csrf_token - нужен для POST-форм;
● if ... else … endif - условный тег
● for … in … endfor - цикл;
● forloop - объект с описанием текущей
итерации цикла, работает только внутри
for … endfor
Шаблон-заголовок
<div class="row">
<a href="{% url 'about' %}">About us</a>
Cart: {% if order %}
<a href="{% url 'cart' %}">{{ order.price }}$</a>
{% else %}empty{% endif %}
</div>
Страница списка товаров ч.1
{% extends 'base.html' %}
{% block title %}Shop{% endblock %}
{% block body %}
<div class="row">
{% for category in categories %}
<a href="{% url 'products' category.id %}>{{ category }}</a>
{% endfor %}
</div>
{% for product in products %}
{% if not forloop.counter|divisibleby:"2" %}
<div class="row">
{% endif %}
Страница списка товаров ч.2<div class="six columns">
<div>
<img src="{{ product.image.url }}" alt="{{ product }}">
</div>
<div>
<a href="{% url 'product' product.id %}"><h4>{{ product }}</h4></a>
</div>
<div>
{{ product.price }}$
</div>
<form action="{% url 'add_to_cart' %}" method="post">
{% csrf_token %}
<input type="hidden" value="{{ product.id }}">
<input type="submit" value="Add to cart">
</form>
</div>
{% if forloop.counter|divisibleby:"2" %}
</div>
{% endif %}
{% endfor %}
{% endblock %}
Страница товара
{% extends 'base.html' %}
{% block title %}{{ product }}{% endblock %}
{% block content %}
<div><img src="{{ product.image.url }}" alt="{{ product }}"></div>
<div>
<h3>{{ product }}</h3>
</div>
<div>{{ product.price }}$</div>
<div>{{ product.description }}</div>
<form action="{% url 'add_to_cart' %}" method="post">
{% csrf_token %}
<input type="hidden" value="{{ product.id }}">
<input type="submit" value="Add to cart">
</form>
{% endblock %}
Корзина ч.1
{% extends 'base.html' %}
{% block title %}Cart{% endblock %}
{% block body %}
<form action="{% url 'cart' %}" method="post">
{% csrf_token %}
<div class="row">
{{ form.as_ul }}
<input type="submit" value="Update order">
</div>
Корзина ч.2
<div class="row">
{% for position_form in formset %}
{{ position_form.as_ul }}
{% endfor %}
</div>
<input type="submit" value="Update order">
</form>
<div class="row">
<form action="{% url 'finish' %}">
{% csrf_token %}
<input type="submit" value="Finish order">
</form>
</div>
{% endblock %}
Страница завершения заказа
{% extends 'base.html' %}
{% block title %}Thank you!{% endblock %}
{% block content %}
Thank you! Your order is {{ order.id }}. You will not be called soon.
{% endblock %}
Страница About us
{% extends 'base.html' %}
{% block title %}About us{% endblock %}
{% block content %}
Glad to see you here!
{% endblock %}
Вопросы?
Ссылки
● https://github.com/SergeyBurma/kranonit-
shop
● https://www.djangoproject.com/
● http://djbook.ru/
● https://pypi.python.org/pypi?%3Aaction=sear
ch&term=django&submit=search
● http://habrahabr.ru/post/159575/
● https://www.jetbrains.com/pycharm/
Спасибо!
Recommended