SlideShare a Scribd company logo
РАЗРАБОТКА РАСШИРЯЕМЫХ
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

More Related Content

PPTX
Meet Magento Belarus debug Pavel Novitsky (rus)
PPTX
Реализация шаблонов корпоративных приложений в Magento
PPTX
Индексирование в Magento
PPT
Dependency injection, phemto
PPTX
Все дороги ведут в Checkout
PPTX
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
PDF
Pycon Russia 2013 - Разработка через тестирование в Python и Django
PDF
Лекция #5. Введение в язык программирования Python 3
Meet Magento Belarus debug Pavel Novitsky (rus)
Реализация шаблонов корпоративных приложений в Magento
Индексирование в Magento
Dependency injection, phemto
Все дороги ведут в Checkout
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
Pycon Russia 2013 - Разработка через тестирование в Python и Django
Лекция #5. Введение в язык программирования Python 3

What's hot (17)

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

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

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

More from MoscowDjango (10)

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