kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно

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