РАЗРАБОТКА РАСШИРЯЕМЫХDJANGO-ПРИЛОЖЕНИЙВладимир Филонов
ЧТО ЭТО И ЗАЧЕМ?  Расширяемость – возможность добавления   функционала при помощи   API, предоставляемого приложением  Про...
DJANGO - РАСШИРЯЕМОЕ ПРИЛОЖЕНИЕ :) Любое приложение для django - по сути  расширение функционала при помощи API. Благода...
ПРАКТИКУМ   Представим, что нам надо разработать    платформу Интернет-магазина# catalog.modelsclass Category(models.Mode...
ПРАКТИКУМ#shop.modelsclass Order(models.Model):  customer = models.CharField(max_length=128)  email = models.EmailField() ...
ПРАКТИКУМ   А что если нам понадобятся дополнительные    услуги по заказам?     Доставка – обязательно понадобиться    ...
ОБОБЩИМ ТРЕБОВАНИЯ К УСЛУГЕ Название Описание Цена – может статичная, или зависеть от заказа Статус выполнения Дополн...
КАК НАМ ВСЕ ЭТО ОРГАНИЗОВАТЬ?            Услуга                Бэкенд  Заказ     Услуга    Диспетчер   Бэкенд            У...
ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ#shop.modelsclass OrderService(models.Model):   order = models.ForeignKey("Order")   service = m...
ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ# А можно и не хранитьclass OrderService(models.Model):   order = models.ForeignKey("Order")   b...
САМОЕ ИНТЕРЕСНОЕ Итак, нам осталось сделать базовый класс для  бэкенда и диспетчер Какой функционал нам понадобиться?   ...
БАЗОВЫЙ КЛАССclass BaseService(object):   has_form = False  def __init__(self, order=None, data=None):    self.data = data...
И ДИСПЕТЧЕР#Построение списка бэкендов#Вариант первый – мы заранее знаем список плагинов#settingssettings.SHOP_SERVICES_BA...
И ДИСПЕТЧЕР#Вариант второй – загрузка только тех модулей, которые указаны в БДdef get_backends(init=False, initial_data=No...
И ДИСПЕТЧЕР#Вариант третий – инспектирование модуля для поиска плагиновimport inspectimport pkgutilfrom django.utils.impor...
И ДИСПЕТЧЕР#Получение класса бэкенда по имени#Если бэкенда нет, мы можем или возвращать Nonedef get_backend(name, init=Fal...
ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕclass ProcessOrderView(View):   def get(self, *args, **kwargs):     context = {        "order_for...
ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕdef post(self, *args, **kwargs):     order_form = OrderForm(self.request.POST)     valid = True  ...
ПОПРОБУЕМ СОБРАТЬ ЭТО ВСЕ#Шаблон#templates/shop/order_process.html{% extends "shop.html" %}{% block content %}<form method...
ЧТО ПОЛУЧИЛОСЬ?
СДЕЛАЕМ ПРОСТУЮ УСЛУГУ…#shop.services.simple_deliveryclass SimpleDelivery (BaseService):   has_form = True   keyword = "si...
ЧТО ПОЛУЧИЛОСЬ?
А ТЕПЕРЬ ЕЩЕ ОДНУclass SingingCourier(BaseService):   has_form = False   keyword = "singing_courier"  def get_title(self):...
ЧТО ПОЛУЧИЛОСЬ?
ЖМЕМ ОТПРАВИТЬ
С ЗАПОЛНЕННЫМИ ПОЛЯМИ
ПРОВЕРИМ ЧТО СОХРАНИЛОСЬ>>> from shop.models import Order>>> order = Order.objects.latest("id")>>> vars(order){customer: u...
ЧТО ОСТАЛОСЬ? Интеграция с contrib.admin Редактирование данных Работа со статусами И еще много всего, но уже не сегодн...
СПАСИБО!             Email: i@vladimir.filonov.nameКод: https://bitbucket.org/VladimirFilonov/django-shop
Upcoming SlideShare
Loading in …5
×

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

1,987 views

Published on

0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,987
On SlideShare
0
From Embeds
0
Number of Embeds
87
Actions
Shares
0
Downloads
23
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

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

  1. 1. РАЗРАБОТКА РАСШИРЯЕМЫХDJANGO-ПРИЛОЖЕНИЙВладимир Филонов
  2. 2. ЧТО ЭТО И ЗАЧЕМ? Расширяемость – возможность добавления функционала при помощи API, предоставляемого приложением Простой пример  AUTHENTICATION_BACKENDS в contrib.auth Решает проблемы:  Повторное использование в различных условиях  Изменение логики приложения, без вмешательства в основной код
  3. 3. DJANGO - РАСШИРЯЕМОЕ ПРИЛОЖЕНИЕ :) Любое приложение для django - по сути расширение функционала при помощи API. Благодаря этому, в django есть все необходимые инструменты и множество примеров django.utils.importlib.import_module django.utils.module_loading.module_has_submodule
  4. 4. ПРАКТИКУМ Представим, что нам надо разработать платформу Интернет-магазина# catalog.modelsclass 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. 5. ПРАКТИКУМ#shop.modelsclass 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. 6. ПРАКТИКУМ А что если нам понадобятся дополнительные услуги по заказам?  Доставка – обязательно понадобиться  Упаковка  Еще что-нибудь Причем, эти услуги могут быть разными, для разных ИМ на базе нашей платформы И мы даже не можем предсказать, какие именно
  7. 7. ОБОБЩИМ ТРЕБОВАНИЯ К УСЛУГЕ Название Описание Цена – может статичная, или зависеть от заказа Статус выполнения Дополнительная информация от клиента
  8. 8. КАК НАМ ВСЕ ЭТО ОРГАНИЗОВАТЬ? Услуга Бэкенд Заказ Услуга Диспетчер Бэкенд Услуга Бэкенд
  9. 9. ПРИВЯЗКА К ЗАКАЗУ - МОДЕЛЬ#shop.modelsclass 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. 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. 11. САМОЕ ИНТЕРЕСНОЕ Итак, нам осталось сделать базовый класс для бэкенда и диспетчер Какой функционал нам понадобиться?  Вычисление цены  Получение, сохранение и обработка дополнительной информации  Получение списка доступных статусов  Реакция на смену статусов
  12. 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. 13. И ДИСПЕТЧЕР#Построение списка бэкендов#Вариант первый – мы заранее знаем список плагинов#settingssettings.SHOP_SERVICES_BACKENDS = { "simple_delivery" : "shop.services.delivery.SimpleDelivery"}#shop.utilsdef 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. 14. И ДИСПЕТЧЕР#Вариант второй – загрузка только тех модулей, которые указаны в БДdef get_backends(init=False, initial_data=None): for service in Service.objects.all(): #Принцип тот же что и в первом варианте …
  15. 15. И ДИСПЕТЧЕР#Вариант третий – инспектирование модуля для поиска плагиновimport inspectimport pkgutilfrom django.utils.importlib import import_modulefrom shop import servicesdef 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. 16. И ДИСПЕТЧЕР#Получение класса бэкенда по имени#Если бэкенда нет, мы можем или возвращать Nonedef 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. 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. 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. 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 }}"{% ifservice.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 %}
  20. 20. ЧТО ПОЛУЧИЛОСЬ?
  21. 21. СДЕЛАЕМ ПРОСТУЮ УСЛУГУ…#shop.services.simple_deliveryclass 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._formclass SimpleDeliveryForm(forms.Form): address = forms.CharField(widget=forms.Textarea, label=u"Адрес", required=True) time = forms.CharField(label=u"Удобное время")
  22. 22. ЧТО ПОЛУЧИЛОСЬ?
  23. 23. А ТЕПЕРЬ ЕЩЕ ОДНУclass SingingCourier(BaseService): has_form = False keyword = "singing_courier" def get_title(self): return u"Поющий курьер" def get_description(self): return u"Курьер споет вам любую песню на ваш выбор"
  24. 24. ЧТО ПОЛУЧИЛОСЬ?
  25. 25. ЖМЕМ ОТПРАВИТЬ
  26. 26. С ЗАПОЛНЕННЫМИ ПОЛЯМИ
  27. 27. ПРОВЕРИМ ЧТО СОХРАНИЛОСЬ>>> from shop.models import Order>>> order = Order.objects.latest("id")>>> vars(order){customer: utest, phone: u, _state: <django.db.models.base.ModelState object at0x89607ec>, id: 1, email: uexample@example.com}>>> order.orderservice_set.count()1>>> service = order.orderservice_set.latest("id")>>> service.backendusimple_delivery>>> print json.loads(service.data){uaddress: uМосква, Малый Конюшковский переулок, дом 2, utime: uс 19 до 22}
  28. 28. ЧТО ОСТАЛОСЬ? Интеграция с contrib.admin Редактирование данных Работа со статусами И еще много всего, но уже не сегодня =)
  29. 29. СПАСИБО! Email: i@vladimir.filonov.nameКод: https://bitbucket.org/VladimirFilonov/django-shop

×