SlideShare ist ein Scribd-Unternehmen logo
1 von 29
РАЗРАБОТКА РАСШИРЯЕМЫХ
DJANGO-ПРИЛОЖЕНИЙ
Владимир Филонов
ЧТО ЭТО И ЗАЧЕМ?
  Расширяемость – возможность добавления
   функционала при помощи
   API, предоставляемого приложением

  Простой пример
   AUTHENTICATION_BACKENDS в contrib.auth


  Решает проблемы:
   Повторное использование в различных условиях
   Изменение логики приложения, без
    вмешательства в основной код
DJANGO - РАСШИРЯЕМОЕ ПРИЛОЖЕНИЕ :)
 Любое приложение для django - по сути
  расширение функционала при помощи API.
 Благодаря этому, в django есть все необходимые
  инструменты и множество примеров

 django.utils.importlib.import_module
 django.utils.module_loading.module_has_submodule
ПРАКТИКУМ
   Представим, что нам надо разработать
    платформу Интернет-магазина

# catalog.models
class Category(models.Model):
    title = models.CharField(max_length=32)
    slug = models.SlugField(max_length=32 , unique=True)


class Product(models.Model):
    title = models.CharField(max_length=32)
    slug = models.SlugField(max_length=32, unique=True)
    category = models.ForeignKey("Category")
    price = models.DecimalField(max_digits=10, decimal_places=2)
ПРАКТИКУМ
#shop.models
class Order(models.Model):
  customer = models.CharField(max_length=128)
  email = models.EmailField()
  phone = models.CharField(max_length=32, blank=True, null=True)


class OrderItem(models.Model):
  order = models.ForeignKey("Order")
  item = models.ForeignKey("catalog.Product")
  amount = models.PositiveSmallIntegerField(default=1)
  price = models.DecimalField(max_digits=10, decimal_places=2)
ПРАКТИКУМ
   А что если нам понадобятся дополнительные
    услуги по заказам?
     Доставка – обязательно понадобиться
     Упаковка
     Еще что-нибудь


 Причем, эти услуги могут быть разными, для
  разных ИМ на базе нашей платформы
 И мы даже не можем предсказать, какие именно
ОБОБЩИМ ТРЕБОВАНИЯ К УСЛУГЕ
 Название
 Описание

 Цена – может статичная, или зависеть от заказа

 Статус выполнения

 Дополнительная информация от клиента
КАК НАМ ВСЕ ЭТО ОРГАНИЗОВАТЬ?


            Услуга                Бэкенд


  Заказ     Услуга    Диспетчер   Бэкенд


            Услуга                Бэкенд
ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ
#shop.models
class OrderService(models.Model):
   order = models.ForeignKey("Order")
   service = models.ForeignKey("Service")
   status = models.CharField(max_length=32, blank=True, default="")
   data = models.TextField() #Мы будем хранить данные в JSON

# Можно хранить сервисы в базе
class Service(models.Model):
   title = models.CharField(max_length=32)
   description = models.TextField()
   base_price = models.DecimalField(max_digits=10, decimal_places=2)
   backend = models.CharField(max_length=32)
   active = models.BooleanField(default=False)
ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ
# А можно и не хранить

class OrderService(models.Model):
   order = models.ForeignKey("Order")
   backend = models.CharField(max_length=32)
   status = models.CharField(max_length=32, blank=True, default="")
   data = models.TextField() #Мы будем хранить данные в JSON
САМОЕ ИНТЕРЕСНОЕ
 Итак, нам осталось сделать базовый класс для
  бэкенда и диспетчер
 Какой функционал нам понадобиться?
     Вычисление цены
     Получение, сохранение и обработка
      дополнительной информации
     Получение списка доступных статусов
     Реакция на смену статусов
БАЗОВЫЙ КЛАСС
class BaseService(object):
   has_form = False

  def __init__(self, order=None, data=None):
    self.data = data
    self.order = order

  def get_title(self):
    return self.__class__.__name__

  def get_description(self):
    return ""

  def get_statuses(self):
    return []

  def calculate_price(self, base_price):
    return base_price

  def status_changed(self, old_status, new_status):
    pass

  def get_form(self):
     return None

  def get_template(self):
    return None
И ДИСПЕТЧЕР
#Построение списка бэкендов
#Вариант первый – мы заранее знаем список плагинов

#settings
settings.SHOP_SERVICES_BACKENDS = {
  "simple_delivery" : "shop.services.delivery.SimpleDelivery"
}

#shop.utils
def get_backends(init=False, initial_data=None):
  backends = []
  for backend_key in settings.SHOP_SERVICES_BACKENDS:
     try:
          path = settings.SHOP_SERVICES_BACKENDS[backend_key]
          i = path.rfind('.')
          module, attr = path[:i], path[i+1:]
          mod = import_module()
          cls = getattr(mod, attr)
          if init:
              backends.append(cls(data=initial_data))
          else:
              backends.append(cls)
     except ImportError:
        continue
   return backends
И ДИСПЕТЧЕР
#Вариант второй – загрузка только тех модулей, которые указаны в БД
def get_backends(init=False, initial_data=None):
  for service in Service.objects.all():
     #Принцип тот же что и в первом варианте
     …
И ДИСПЕТЧЕР
#Вариант третий – инспектирование модуля для поиска плагинов
import inspect
import pkgutil
from django.utils.importlib import import_module

from shop import services

def get_backends(init=False,pkgutil.iter_modules(path=None, prefix='')
                                initial_data=None, as_list=True):
   if as_list:                                 Возвращает кортеж
       backends = []                 import_module(name, package=None)
   else:
                                  (module_loader, name, ispkg) для всех
       backends = {}
                             Импортирует модуль. Удобство в том, что если
                                                   подмодулей
                                      передать имя начинающееся с точки
                                  inspect.getmembers(object[, predicate])
   for mod in pkgutil.iter_modules(services.__path__):
                                 Возвращаетто поисквсех членов объекта
                                      ".name", список для импорта будет
       module = import_module('.{0}'.format(mod[1]), 'shop.services')
                                  производиться не по sys.path, а только в
       predicate = lambda x: inspect.isclass(x) and issubclass(x, services.BaseService) and not
                                (аттрибуты, функции, классы и т.д.). Если
x == services.BaseService
                                   указанном во втором аргументе пакете.
       for name, backend in inspect.getmembers(module,аргумента передать
                                   качестве второго predicate):
          if init:          функцию-ограничитель, то inspect.getmembers
              value = backend(data=initial_data) те члены, для которых predicate
                             вернет только
          else:
             value = backend                        вернет True
          if as_list:
              backends.append(value)
          else:
              backends.update({backend.keyword: value})
   return backends
И ДИСПЕТЧЕР
#Получение класса бэкенда по имени
#Если бэкенда нет, мы можем или возвращать None
def get_backend(name, init=False, initial_data=None):
  return get_backends(init, initial_data).get(name)

#Или же
def get_backend(name, init=False, initial_data=None):
  backend = get_backends(init, initial_data) .get(name)
  if not backend:
      raise ImproperlyConfigured(u"There is no service backend named `{0}`".format(name))
ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ
class ProcessOrderView(View):
   def get(self, *args, **kwargs):
     context = {
        "order_form": OrderForm(),
        "services": get_backends(init=True)
     }
     return self.render_to_response(context)

  def get_services(self):
    if not hasattr(self, "_submitted_services"):
        services = []
        for service_name in self.request.POST.getlist("service"):
          service = get_backend(service_name, init=True, initial_data=self.request.POST)
          services.append(service)
        self._submitted_services = services
    return self._submitted_services

  def all_services_valid(self):
    valid = True
    for service in self.get_services():
       if not service.get_form().is_valid():
           valid = False
    return valid
ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ
def post(self, *args, **kwargs):
     order_form = OrderForm(self.request.POST)
     valid = True
     if order_form.is_valid() and self.all_services_valid():
         order = order_form.save()
         for service in self.get_services():
              form_data = json.dumps(service.get_form().cleaned_data)
              OrderService.objects.create(order=order, backend=service.keyword, data=form_data)
         return HttpResponseRedirect("/shop/success/")
     else:
         valid = False
     if not valid:
         services = self.get_filled_services()
         context = {
            "order_form": order_form,
            "services": services
         }
         return self.render_to_response(context)

 def get_filled_services(self):
   services = []
   for service in get_backends():
      if service.keyword in self.request.POST.getlist("service"):
          service.checked = True
          services.append(service(data=self.request.POST))
      else:
          service.checked = False
          services.append(service)
   return services
ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ
#Шаблон
#templates/shop/order_process.html
{% extends "shop.html" %}
{% block content %}
<form method="POST">{% csrf_token %}
   {{ order_form.as_p }}
   {% for service in services %}
      <div class="service {{ service.keyword }}">
        <input type="checkbox" name="service" value="{{ service.keyword }}"{% if
service.checked %} checked{% endif %}><label>{{ service.get_title }}</label>
        <div><small>{{ service.get_description }}</small></div>
        {% if service.has_form %}
           {{ service.get_form.as_p }}
        {% endif %}
      </div>
   {% endfor %}
   <input type="submit">
</form>{% endblock %}
ЧТО ПОЛУЧИЛОСЬ?
СДЕЛАЕМ ПРОСТУЮ УСЛУГУ…
#shop.services.simple_delivery
class SimpleDelivery (BaseService):
   has_form = True
   keyword = "simple_delivery"

  def get_statuses(self):
    return ["planned", "in process", "done"]

  def calculate_price(self, base_price, order):
    return base_price

  def get_form_class(self):
    return SimpleDeliveryForm

  def get_form(self):
    if not hasattr(self, "_form"):
        self._form = self.get_form_class()(self.data, prefix=self.__class__.__name__)
    return self._form

class SimpleDeliveryForm(forms.Form):
   address = forms.CharField(widget=forms.Textarea, label=u"Адрес", required=True)
   time = forms.CharField(label=u"Удобное время")
ЧТО ПОЛУЧИЛОСЬ?
А ТЕПЕРЬ ЕЩЕ ОДНУ
class SingingCourier(BaseService):
   has_form = False
   keyword = "singing_courier"

  def get_title(self):
    return u"Поющий курьер"

  def get_description(self):
    return u"Курьер споет вам любую песню на ваш выбор"
ЧТО ПОЛУЧИЛОСЬ?
ЖМЕМ ОТПРАВИТЬ
С ЗАПОЛНЕННЫМИ ПОЛЯМИ
ПРОВЕРИМ ЧТО СОХРАНИЛОСЬ
>>> from shop.models import Order
>>> order = Order.objects.latest("id")
>>> vars(order)
{'customer': u'test', 'phone': u'', '_state': <django.db.models.base.ModelState object at
0x89607ec>, 'id': 1, 'email': u'example@example.com'}
>>> order.orderservice_set.count()
1
>>> service = order.orderservice_set.latest("id")
>>> service.backend
u'simple_delivery'
>>> print json.loads(service.data)
{u'address': u'Москва, Малый Конюшковский переулок, дом 2', u'time': u'с 19 до 22'}
ЧТО ОСТАЛОСЬ?
 Интеграция с contrib.admin
 Редактирование данных

 Работа со статусами

 И еще много всего, но уже не сегодня =)
СПАСИБО!
             Email: i@vladimir.filonov.name
Код: https://bitbucket.org/VladimirFilonov/django-shop

Weitere ähnliche Inhalte

Was ist angesagt?

Andrew Borisenko "Magic of Vue.js""
Andrew Borisenko  "Magic of Vue.js""Andrew Borisenko  "Magic of Vue.js""
Andrew Borisenko "Magic of Vue.js""OdessaJS Conf
 
Form api в drupal 7
Form api в drupal 7Form api в drupal 7
Form api в drupal 7dimateus
 
Yii development
Yii developmentYii development
Yii developmentMageCloud
 
Быть в 10 раз эффективнее благодаря Groovy
Быть в 10 раз эффективнее благодаря GroovyБыть в 10 раз эффективнее благодаря Groovy
Быть в 10 раз эффективнее благодаря GroovyEvgeny Kompaniyets
 
Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...
Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...
Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...MageCloud
 
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Vasya Petrov
 
2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк
2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк
2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворкОмские ИТ-субботники
 
[JAM 1.1] Clean Code (Paul Malikov)
[JAM 1.1] Clean Code (Paul Malikov)[JAM 1.1] Clean Code (Paul Malikov)
[JAM 1.1] Clean Code (Paul Malikov)Evgeny Kaziak
 
Enterprise Patterns in Magento
Enterprise Patterns in MagentoEnterprise Patterns in Magento
Enterprise Patterns in MagentoVrann Tulika
 
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Vasya Petrov
 
Эффективное программирование на NodeJS
Эффективное программирование на NodeJSЭффективное программирование на NodeJS
Эффективное программирование на NodeJSYura Bogdanov
 
Производительность в Django
Производительность в DjangoПроизводительность в Django
Производительность в DjangoMoscowDjango
 
Web осень 2013 лекция 4
Web осень 2013 лекция 4Web осень 2013 лекция 4
Web осень 2013 лекция 4Technopark
 
Как программировать на JavaScript и не выстрелить себе в ногу
Как программировать на JavaScript и не выстрелить себе в ногуКак программировать на JavaScript и не выстрелить себе в ногу
Как программировать на JavaScript и не выстрелить себе в ногуAndreyGeonya
 

Was ist angesagt? (18)

Andrew Borisenko "Magic of Vue.js""
Andrew Borisenko  "Magic of Vue.js""Andrew Borisenko  "Magic of Vue.js""
Andrew Borisenko "Magic of Vue.js""
 
Form api в drupal 7
Form api в drupal 7Form api в drupal 7
Form api в drupal 7
 
Yii development
Yii developmentYii development
Yii development
 
Быть в 10 раз эффективнее благодаря Groovy
Быть в 10 раз эффективнее благодаря GroovyБыть в 10 раз эффективнее благодаря Groovy
Быть в 10 раз эффективнее благодаря Groovy
 
Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...
Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...
Профилирование и оптимизация фреймворков высоконагруженных систем на примере ...
 
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1
 
2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк
2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк
2014-08-02 01 Егор Непомнящих. jWidget - очередной MV*-фреймворк
 
[JAM 1.1] Clean Code (Paul Malikov)
[JAM 1.1] Clean Code (Paul Malikov)[JAM 1.1] Clean Code (Paul Malikov)
[JAM 1.1] Clean Code (Paul Malikov)
 
Enterprise Patterns in Magento
Enterprise Patterns in MagentoEnterprise Patterns in Magento
Enterprise Patterns in Magento
 
Perl: Symbol table
Perl: Symbol tablePerl: Symbol table
Perl: Symbol table
 
Zend Framework и Doctrine
Zend Framework и DoctrineZend Framework и Doctrine
Zend Framework и Doctrine
 
Лекция #7. Django ORM
Лекция #7. Django ORMЛекция #7. Django ORM
Лекция #7. Django ORM
 
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1
 
Эффективное программирование на NodeJS
Эффективное программирование на NodeJSЭффективное программирование на NodeJS
Эффективное программирование на NodeJS
 
Производительность в Django
Производительность в DjangoПроизводительность в Django
Производительность в Django
 
Web осень 2013 лекция 4
Web осень 2013 лекция 4Web осень 2013 лекция 4
Web осень 2013 лекция 4
 
Agile Instrumentation
Agile InstrumentationAgile Instrumentation
Agile Instrumentation
 
Как программировать на JavaScript и не выстрелить себе в ногу
Как программировать на JavaScript и не выстрелить себе в ногуКак программировать на JavaScript и не выстрелить себе в ногу
Как программировать на JavaScript и не выстрелить себе в ногу
 

Ähnlich wie Разработка расширяемых приложений на Django

Web весна 2012 лекция 7
Web весна 2012 лекция 7Web весна 2012 лекция 7
Web весна 2012 лекция 7Technopark
 
Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Sergey Schetinin
 
Web осень 2013 лекция 8
Web осень 2013 лекция 8Web осень 2013 лекция 8
Web осень 2013 лекция 8Technopark
 
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...it-people
 
Разработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconruРазработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconruJetStyle
 
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким ХалиловWebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким ХалиловGeeksLab Odessa
 
ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...
ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...
ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...ZFConf Conference
 
Организация работы с API на Vue.js, Виталий Копачёв
Организация работы с API на Vue.js, Виталий КопачёвОрганизация работы с API на Vue.js, Виталий Копачёв
Организация работы с API на Vue.js, Виталий КопачёвMail.ru Group
 
Web весна 2013 лекция 4
Web весна 2013 лекция 4Web весна 2013 лекция 4
Web весна 2013 лекция 4Technopark
 
Yii2
Yii2Yii2
Yii2Noveo
 
JS утиліти WordPress на практиці
JS утиліти WordPress на практиціJS утиліти WordPress на практиці
JS утиліти WordPress на практиціShtrih Sruleg
 
Magento code debugging
Magento code debuggingMagento code debugging
Magento code debuggingaheadWorks
 
12 - Web-технологии. Django модели
12 - Web-технологии. Django модели12 - Web-технологии. Django модели
12 - Web-технологии. Django моделиRoman Brovko
 
Web осень 2013 лекция 6
Web осень 2013 лекция 6Web осень 2013 лекция 6
Web осень 2013 лекция 6Technopark
 
Top 10 problems supporting Magento customers
Top 10 problems supporting Magento customersTop 10 problems supporting Magento customers
Top 10 problems supporting Magento customersaheadWorks
 
Web осень 2012 лекция 4
Web осень 2012 лекция 4Web осень 2012 лекция 4
Web осень 2012 лекция 4Technopark
 
10 - Web-технологии. MVC фреймворки (продолжение)
10 - Web-технологии. MVC фреймворки (продолжение)10 - Web-технологии. MVC фреймворки (продолжение)
10 - Web-технологии. MVC фреймворки (продолжение)Roman Brovko
 
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...HappyDev
 

Ähnlich wie Разработка расширяемых приложений на Django (20)

Web весна 2012 лекция 7
Web весна 2012 лекция 7Web весна 2012 лекция 7
Web весна 2012 лекция 7
 
Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование
 
Web осень 2013 лекция 8
Web осень 2013 лекция 8Web осень 2013 лекция 8
Web осень 2013 лекция 8
 
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
 
Разработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconruРазработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconru
 
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким ХалиловWebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
 
ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...
ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...
ZFConf 2011: Разделение труда: Организация многозадачной, распределенной сист...
 
Организация работы с API на Vue.js, Виталий Копачёв
Организация работы с API на Vue.js, Виталий КопачёвОрганизация работы с API на Vue.js, Виталий Копачёв
Организация работы с API на Vue.js, Виталий Копачёв
 
Web весна 2013 лекция 4
Web весна 2013 лекция 4Web весна 2013 лекция 4
Web весна 2013 лекция 4
 
Yii2
Yii2Yii2
Yii2
 
JS утиліти WordPress на практиці
JS утиліти WordPress на практиціJS утиліти WordPress на практиці
JS утиліти WordPress на практиці
 
Magento code debugging
Magento code debuggingMagento code debugging
Magento code debugging
 
12 - Web-технологии. Django модели
12 - Web-технологии. Django модели12 - Web-технологии. Django модели
12 - Web-технологии. Django модели
 
Render API.
Render API.Render API.
Render API.
 
Web осень 2013 лекция 6
Web осень 2013 лекция 6Web осень 2013 лекция 6
Web осень 2013 лекция 6
 
Top 10 problems supporting Magento customers
Top 10 problems supporting Magento customersTop 10 problems supporting Magento customers
Top 10 problems supporting Magento customers
 
Web осень 2012 лекция 4
Web осень 2012 лекция 4Web осень 2012 лекция 4
Web осень 2012 лекция 4
 
бегун
бегунбегун
бегун
 
10 - Web-технологии. MVC фреймворки (продолжение)
10 - Web-технологии. MVC фреймворки (продолжение)10 - Web-технологии. MVC фреймворки (продолжение)
10 - Web-технологии. MVC фреймворки (продолжение)
 
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
 

Mehr von MoscowDjango

Тестирование и Django
Тестирование и DjangoТестирование и Django
Тестирование и DjangoMoscowDjango
 
Пример fuzzy testing для поиска URL в тексте
Пример fuzzy testing для поиска URL в текстеПример fuzzy testing для поиска URL в тексте
Пример fuzzy testing для поиска URL в текстеMoscowDjango
 
TDD или как я стараюсь писать код
TDD или как я стараюсь писать кодTDD или как я стараюсь писать код
TDD или как я стараюсь писать кодMoscowDjango
 
Cyclone + Eventsource (realtime push-сообщения)
Cyclone + Eventsource (realtime push-сообщения)Cyclone + Eventsource (realtime push-сообщения)
Cyclone + Eventsource (realtime push-сообщения)MoscowDjango
 
Django на Android
Django на AndroidDjango на Android
Django на AndroidMoscowDjango
 
Работа со статикой в Django
Работа со статикой в DjangoРабота со статикой в Django
Работа со статикой в DjangoMoscowDjango
 
Class Based Generic Views в Django
Class Based Generic Views в DjangoClass Based Generic Views в Django
Class Based Generic Views в DjangoMoscowDjango
 
Простой и удобный деплоймент проекта
Простой и удобный деплоймент проектаПростой и удобный деплоймент проекта
Простой и удобный деплоймент проектаMoscowDjango
 
Django South. Миграция баз данных.
Django South. Миграция баз данных.  Django South. Миграция баз данных.
Django South. Миграция баз данных. MoscowDjango
 
Журнальная вёрстка в Django
Журнальная вёрстка в DjangoЖурнальная вёрстка в Django
Журнальная вёрстка в DjangoMoscowDjango
 

Mehr von MoscowDjango (10)

Тестирование и Django
Тестирование и DjangoТестирование и Django
Тестирование и Django
 
Пример fuzzy testing для поиска URL в тексте
Пример fuzzy testing для поиска URL в текстеПример fuzzy testing для поиска URL в тексте
Пример fuzzy testing для поиска URL в тексте
 
TDD или как я стараюсь писать код
TDD или как я стараюсь писать кодTDD или как я стараюсь писать код
TDD или как я стараюсь писать код
 
Cyclone + Eventsource (realtime push-сообщения)
Cyclone + Eventsource (realtime push-сообщения)Cyclone + Eventsource (realtime push-сообщения)
Cyclone + Eventsource (realtime push-сообщения)
 
Django на Android
Django на AndroidDjango на Android
Django на Android
 
Работа со статикой в Django
Работа со статикой в DjangoРабота со статикой в Django
Работа со статикой в Django
 
Class Based Generic Views в Django
Class Based Generic Views в DjangoClass Based Generic Views в Django
Class Based Generic Views в Django
 
Простой и удобный деплоймент проекта
Простой и удобный деплоймент проектаПростой и удобный деплоймент проекта
Простой и удобный деплоймент проекта
 
Django South. Миграция баз данных.
Django South. Миграция баз данных.  Django South. Миграция баз данных.
Django South. Миграция баз данных.
 
Журнальная вёрстка в Django
Журнальная вёрстка в DjangoЖурнальная вёрстка в Django
Журнальная вёрстка в Django
 

Разработка расширяемых приложений на Django

  • 2. ЧТО ЭТО И ЗАЧЕМ? Расширяемость – возможность добавления функционала при помощи API, предоставляемого приложением Простой пример  AUTHENTICATION_BACKENDS в contrib.auth Решает проблемы:  Повторное использование в различных условиях  Изменение логики приложения, без вмешательства в основной код
  • 3. DJANGO - РАСШИРЯЕМОЕ ПРИЛОЖЕНИЕ :)  Любое приложение для django - по сути расширение функционала при помощи API.  Благодаря этому, в django есть все необходимые инструменты и множество примеров  django.utils.importlib.import_module  django.utils.module_loading.module_has_submodule
  • 4. ПРАКТИКУМ  Представим, что нам надо разработать платформу Интернет-магазина # catalog.models class Category(models.Model): title = models.CharField(max_length=32) slug = models.SlugField(max_length=32 , unique=True) class Product(models.Model): title = models.CharField(max_length=32) slug = models.SlugField(max_length=32, unique=True) category = models.ForeignKey("Category") price = models.DecimalField(max_digits=10, decimal_places=2)
  • 5. ПРАКТИКУМ #shop.models class Order(models.Model): customer = models.CharField(max_length=128) email = models.EmailField() phone = models.CharField(max_length=32, blank=True, null=True) class OrderItem(models.Model): order = models.ForeignKey("Order") item = models.ForeignKey("catalog.Product") amount = models.PositiveSmallIntegerField(default=1) price = models.DecimalField(max_digits=10, decimal_places=2)
  • 6. ПРАКТИКУМ  А что если нам понадобятся дополнительные услуги по заказам?  Доставка – обязательно понадобиться  Упаковка  Еще что-нибудь  Причем, эти услуги могут быть разными, для разных ИМ на базе нашей платформы  И мы даже не можем предсказать, какие именно
  • 7. ОБОБЩИМ ТРЕБОВАНИЯ К УСЛУГЕ  Название  Описание  Цена – может статичная, или зависеть от заказа  Статус выполнения  Дополнительная информация от клиента
  • 8. КАК НАМ ВСЕ ЭТО ОРГАНИЗОВАТЬ? Услуга Бэкенд Заказ Услуга Диспетчер Бэкенд Услуга Бэкенд
  • 9. ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ #shop.models class OrderService(models.Model): order = models.ForeignKey("Order") service = models.ForeignKey("Service") status = models.CharField(max_length=32, blank=True, default="") data = models.TextField() #Мы будем хранить данные в JSON # Можно хранить сервисы в базе class Service(models.Model): title = models.CharField(max_length=32) description = models.TextField() base_price = models.DecimalField(max_digits=10, decimal_places=2) backend = models.CharField(max_length=32) active = models.BooleanField(default=False)
  • 10. ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ # А можно и не хранить class OrderService(models.Model): order = models.ForeignKey("Order") backend = models.CharField(max_length=32) status = models.CharField(max_length=32, blank=True, default="") data = models.TextField() #Мы будем хранить данные в JSON
  • 11. САМОЕ ИНТЕРЕСНОЕ  Итак, нам осталось сделать базовый класс для бэкенда и диспетчер  Какой функционал нам понадобиться?  Вычисление цены  Получение, сохранение и обработка дополнительной информации  Получение списка доступных статусов  Реакция на смену статусов
  • 12. БАЗОВЫЙ КЛАСС class BaseService(object): has_form = False def __init__(self, order=None, data=None): self.data = data self.order = order def get_title(self): return self.__class__.__name__ def get_description(self): return "" def get_statuses(self): return [] def calculate_price(self, base_price): return base_price def status_changed(self, old_status, new_status): pass def get_form(self): return None def get_template(self): return None
  • 13. И ДИСПЕТЧЕР #Построение списка бэкендов #Вариант первый – мы заранее знаем список плагинов #settings settings.SHOP_SERVICES_BACKENDS = { "simple_delivery" : "shop.services.delivery.SimpleDelivery" } #shop.utils def get_backends(init=False, initial_data=None): backends = [] for backend_key in settings.SHOP_SERVICES_BACKENDS: try: path = settings.SHOP_SERVICES_BACKENDS[backend_key] i = path.rfind('.') module, attr = path[:i], path[i+1:] mod = import_module() cls = getattr(mod, attr) if init: backends.append(cls(data=initial_data)) else: backends.append(cls) except ImportError: continue return backends
  • 14. И ДИСПЕТЧЕР #Вариант второй – загрузка только тех модулей, которые указаны в БД def get_backends(init=False, initial_data=None): for service in Service.objects.all(): #Принцип тот же что и в первом варианте …
  • 15. И ДИСПЕТЧЕР #Вариант третий – инспектирование модуля для поиска плагинов import inspect import pkgutil from django.utils.importlib import import_module from shop import services def get_backends(init=False,pkgutil.iter_modules(path=None, prefix='') initial_data=None, as_list=True): if as_list: Возвращает кортеж backends = [] import_module(name, package=None) else: (module_loader, name, ispkg) для всех backends = {} Импортирует модуль. Удобство в том, что если подмодулей передать имя начинающееся с точки inspect.getmembers(object[, predicate]) for mod in pkgutil.iter_modules(services.__path__): Возвращаетто поисквсех членов объекта ".name", список для импорта будет module = import_module('.{0}'.format(mod[1]), 'shop.services') производиться не по sys.path, а только в predicate = lambda x: inspect.isclass(x) and issubclass(x, services.BaseService) and not (аттрибуты, функции, классы и т.д.). Если x == services.BaseService указанном во втором аргументе пакете. for name, backend in inspect.getmembers(module,аргумента передать качестве второго predicate): if init: функцию-ограничитель, то inspect.getmembers value = backend(data=initial_data) те члены, для которых predicate вернет только else: value = backend вернет True if as_list: backends.append(value) else: backends.update({backend.keyword: value}) return backends
  • 16. И ДИСПЕТЧЕР #Получение класса бэкенда по имени #Если бэкенда нет, мы можем или возвращать None def get_backend(name, init=False, initial_data=None): return get_backends(init, initial_data).get(name) #Или же def get_backend(name, init=False, initial_data=None): backend = get_backends(init, initial_data) .get(name) if not backend: raise ImproperlyConfigured(u"There is no service backend named `{0}`".format(name))
  • 17. ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ class ProcessOrderView(View): def get(self, *args, **kwargs): context = { "order_form": OrderForm(), "services": get_backends(init=True) } return self.render_to_response(context) def get_services(self): if not hasattr(self, "_submitted_services"): services = [] for service_name in self.request.POST.getlist("service"): service = get_backend(service_name, init=True, initial_data=self.request.POST) services.append(service) self._submitted_services = services return self._submitted_services def all_services_valid(self): valid = True for service in self.get_services(): if not service.get_form().is_valid(): valid = False return valid
  • 18. ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ def post(self, *args, **kwargs): order_form = OrderForm(self.request.POST) valid = True if order_form.is_valid() and self.all_services_valid(): order = order_form.save() for service in self.get_services(): form_data = json.dumps(service.get_form().cleaned_data) OrderService.objects.create(order=order, backend=service.keyword, data=form_data) return HttpResponseRedirect("/shop/success/") else: valid = False if not valid: services = self.get_filled_services() context = { "order_form": order_form, "services": services } return self.render_to_response(context) def get_filled_services(self): services = [] for service in get_backends(): if service.keyword in self.request.POST.getlist("service"): service.checked = True services.append(service(data=self.request.POST)) else: service.checked = False services.append(service) return services
  • 19. ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ #Шаблон #templates/shop/order_process.html {% extends "shop.html" %} {% block content %} <form method="POST">{% csrf_token %} {{ order_form.as_p }} {% for service in services %} <div class="service {{ service.keyword }}"> <input type="checkbox" name="service" value="{{ service.keyword }}"{% if service.checked %} checked{% endif %}><label>{{ service.get_title }}</label> <div><small>{{ service.get_description }}</small></div> {% if service.has_form %} {{ service.get_form.as_p }} {% endif %} </div> {% endfor %} <input type="submit"> </form>{% endblock %}
  • 21. СДЕЛАЕМ ПРОСТУЮ УСЛУГУ… #shop.services.simple_delivery class SimpleDelivery (BaseService): has_form = True keyword = "simple_delivery" def get_statuses(self): return ["planned", "in process", "done"] def calculate_price(self, base_price, order): return base_price def get_form_class(self): return SimpleDeliveryForm def get_form(self): if not hasattr(self, "_form"): self._form = self.get_form_class()(self.data, prefix=self.__class__.__name__) return self._form class SimpleDeliveryForm(forms.Form): address = forms.CharField(widget=forms.Textarea, label=u"Адрес", required=True) time = forms.CharField(label=u"Удобное время")
  • 23. А ТЕПЕРЬ ЕЩЕ ОДНУ class SingingCourier(BaseService): has_form = False keyword = "singing_courier" def get_title(self): return u"Поющий курьер" def get_description(self): return u"Курьер споет вам любую песню на ваш выбор"
  • 27. ПРОВЕРИМ ЧТО СОХРАНИЛОСЬ >>> from shop.models import Order >>> order = Order.objects.latest("id") >>> vars(order) {'customer': u'test', 'phone': u'', '_state': <django.db.models.base.ModelState object at 0x89607ec>, 'id': 1, 'email': u'example@example.com'} >>> order.orderservice_set.count() 1 >>> service = order.orderservice_set.latest("id") >>> service.backend u'simple_delivery' >>> print json.loads(service.data) {u'address': u'Москва, Малый Конюшковский переулок, дом 2', u'time': u'с 19 до 22'}
  • 28. ЧТО ОСТАЛОСЬ?  Интеграция с contrib.admin  Редактирование данных  Работа со статусами  И еще много всего, но уже не сегодня =)
  • 29. СПАСИБО! Email: i@vladimir.filonov.name Код: https://bitbucket.org/VladimirFilonov/django-shop