Разработка
 
 
одностраничных
 веб-­‐приложений
 
 
с
 использованием
 
 
PonyORM
 и
 ReactJS
Александ...
Задача
Разработать
 Single
 Page
 Application
Приложение
 -­‐
 библиотека
 современного
 искусства
Задача
Разработать
 Single
 Page
 Application
Приложение
 -­‐
 библиотека
 современного
 искусства
Особеннос...
Задача
Разработать
 Single
 Page
 Application
Приложение
 -­‐
 библиотека
 современного
 искусства
Особеннос...
Задача
Разработать
 Single
 Page
 Application
Приложение
 -­‐
 библиотека
 современного
 искусства
Особеннос...
1.
 Фронтенд.
 Современные
 фреймворки:
 
• Backbone
 
• AngularJS
 
• KnockoutJS
 
• EmberJS
 
• ReactJS
...
1.
 Фронтенд.
 Современные
 фреймворки:
 
• Backbone
 
• AngularJS
 
• KnockoutJS
 
• EmberJS
 
• ReactJS	...
1.
 Фронтенд.
 Современные
 фреймворки:
 
• Backbone
 
• AngularJS
 
• KnockoutJS
 
• EmberJS
 
• ReactJS	...
Проблемы
 
1. Дублирование
 моделей
 на
 фронтенде
 (уже
 есть
 на
 
бэкенде)
 
2. REST
 может
 гене...
Решения
 
1. Генерировать
 фронтенд
 
модели
 автоматически
 на
 
основе
 моделей
 на
 
бэкенде
 
!
1....
Решения
 
1. Генерировать
 фронтенд
 
модели
 автоматически
 на
 
основе
 моделей
 на
 
бэкенде
 
!
2	...
PonyJS
 
PonyJS
 автоматически
 генерирует
 фронтенд
 модели
 
 
на
 основе
 моделей
 PonyORM
 на
...
Почему
 PonyORM?
o1 = Order.objects.get(pk=1)
print(o1.total, o1.customer.id)
o2 = Order.objects.get(pk=2)
print(o2.tota...
session.query(Product).filter(
 
 
 
 
 (Product.name.startswith('A')
 &
 (Product.image
 ==
 None))
...
База данных
Место
 Pony
 в
 архитектуре
 веб-­‐приложения
PonyJS
Data Access
Business Logic
Service API
Приложение...
Общая
 схема
 взаимодействия
 
Браузер Сервер
/artworks
HTML
/api/artworks
JSON
Первый
 
запрос
AJAX
 запросы
class
 Author(db.En~ty):
 
 
 
 
 id
 =
 PrimaryKey(int,
 auto=True)
 
 
 
 
 name
 =
 R...
AJAX
 запрос
 с
 фронтенда
Браузер Сервер
AJAX
 запрос
 /api/artworks
JSON
pony.load({
 
 
 
  url:
 ...
Обработчик
 на
 бэкенде
Браузер Сервер
JSON
@app.route('/api/artworks')
 
@db_session
 
def
 get_artworks():
 ...
Получаем
 список
 artworks
 на
 фронтенде
Браузер Сервер
AJAX
 запрос
 /api/artworks
JSON
pony.load({
 
 ...
Получили
 на
 фронтенде
 тот
 же
 список
Список
 полноценных
 объектов
 сущностей
Атрибуты
 -­‐
 это
 getter&setters
Переход
 по
 связям
 объекта
class
 Author(db.En~ty):
 
 
 
 
 id
 =
 PrimaryKey(int,
 auto=True)
 
 
 
 
 name
 =
 R...
Добавляем
 объекты
 Author
 
Браузер Сервер
JSON
@app.route('/api/artworks')
 
@db_session
 
def
 get_artworks...
Получаем
 список
 artworks
 на
 фронтенде
Браузер Сервер
AJAX
 запрос
 /api/artworks
JSON
pony.load({
 
 ...
Теперь
 мы
 видим
 автора
Как
 же
 он
 был
 передан?
user data
identity map
schema
Artwork[1] Artwork[2] Artwork[3]
Author[1] Author[2]
Формат
 JSON
 пакета
Оба
 artwork’а
 ссылаются
 
 
на
 один
 и
 тот
 же
 объект
 Author
user data
identity map
schema
Artwork[1] Artwork[2] Artwork[3]
Author[1] Author[2]
Формат
 JSON
 пакета
{
 
 
 ...
Пользовательские
 данные
"data":
 [
 
 
 
 
 
 {
 
 
 
 
 
 
 
 
 
 "pk":
 1,
...
Iden~ty
 Map
 объектов
"objects":
 {
 
 
 
 
 "Ar~st":
 {
 
 
 
 
 
 
 
 
 "1":
 {...
Iden~ty
 Map
 объектов
!
 
 
 
 "Author":
 {
 
 
 
 
 
 
 
 
 "1":
 {
 
 
 
 ...
Схема
 объектов
"schema":
 [
 
  {
 
 
 
 
 
 
 
 
 "name":
 "Author",
 
 
 
 
 	...
Схема
 объектов
!
 
 
 
 {
 
 
 
 
 
 
 
 
 "name":
 "Author",
 
 
 
 
 
 
...
Подгрузка
 дополнительных
 объектов
Браузер Сервер
JSON
AJAX
 запрос
 /api/artworks?page=2
полученные
 объекты
...
Изменим
 атрибут
 title
Можно
 сохранять
 изменения
Браузер Сервер
JSON
AJAX
 запрос
 /api/artworks
изменить
 
создать
 
удалить
 
о...
Теперь
 сохраним
 изменения
Браузер Сервер
JSON
AJAX
 запрос
 /api/artworks
изменить
 
создать
 
удалить
 
о...
Передаваемый
 JSON
{
 
 
 "data":
 null,
 
 
 
 "objects":
 [
 
 
 
 {
 
 
 
 
...
Обработчик
 на
 бэкенде
Браузер Сервер
JSON
AJAX
 запрос
 /api/artworks
изменить
 
создать
 
удалить
 
объек...
А
 как
 же
 права
 доступа?
Права
 доступа
• Проверяются
 на
 сервере
 при
 выполнении

to_json()
 и
 from_json()
 
• Позволяют
 спр...
Права
 доступа
• Гарантируют,
 что
 на
 фронтенд
 передаются

только
 объекты,
 которые
 можно
 видеть

...
Способы
 задания
 прав
 доступа
• На
 уровне
 классов
 
• На
 уровне
 объектов

(row-­‐level
 permission...
На
 основе
 чего
 задаются
 права?
• Группы
 -­‐
 характеризуют
 текущего
 пользователя.
 
Примеры:
 a...
Пример
 задания
 прав
 доступа
with db.permissions_for(Artwork):
allow('view’, group='anybody')
allow('create, edit'...
 Порядок
 вычисления
 прав
 доступа
• Пони
 запрашивает
 группы
 текущего
 пользователя
 и
 
кеширует
...
 Получение
 групп
 текущего
 пользователя
@groups_getter(Author)
def get_author_groups(author):
return ['author']
!
...
Использование
 групп
 для
 задания
 прав
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('c...
 Получение
 ролей
 текущего
 пользователя
@roles_getter(Author, Artwork)
def get_author_roles(author, artwork):
if a...
Использование
 ролей
 для
 задания
 прав
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('c...
Проверка
 роли
 при
 создании
 объекта
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('cre...
 Пример
 использования
 меток
Допустим,
 что
 некоторые
 картины
 
должны
 быть
 скрыты
 от
 обычных
...
class
 Author(db.En~ty):
 
 
 
 
 id
 =
 PrimaryKey(int,
 auto=True)
 
 
 
 
 name
 =
 R...
 Получение
 меток
 объекта
@labels_getter(Artwork)
def get_artwork_labels(artwork):
if not artwork.hidden:
return ['pu...
Пример
 использования
 меток
with db.permissions_for(Artwork):
allow('view', group='anybody',
label='public')
allow('c...
 Автоматические
 фильтры
 запросов
Удобны,
 если
 мы
 хотим
 автоматически
 
добавлять
 условие
 ко
 в...
 Автоматические
 фильтры
 запросов
@default_filter(Artwork)
def public(artwork):
return not artwork.hidden
 Автоматические
 фильтры
 запросов
artworks = Artwork.select(
lambda a: a.author.name == 'Gerhard Richter'
).order_by(...
 Автоматические
 фильтры
 запросов
artworks = Artwork.select(
lambda a: a.author.name == 'Gerhard Richter'
).order_by(...
 
 Отключение
 автоматических
 фильтров
with default_filters_disabled:
artworks = Artwork.select(
lambda a: a.author...
 
 Отключение
 автоматических
 фильтров
with default_filters_disabled:
artworks = Artwork.select(
lambda a: a.author...
Разные
 права
 для
 разных
 сайтов
with db.permissions_for(Artwork):
allow('view', group='anybody',
label='public'...
ReactJS
• ReactJS
 позволяет
 строить
 страницу
 из
 
компонентов
 
• Компоненты
 представляют
 собой
 с...
Пример
 React-­‐компонента
var ArtworkDescription = React.createClass({
render: function () {
var artwork = this.props.a...
Пример
 React-­‐компонента
var ArtworksPage = React.createClass({
render: function () {
var artworkList = …
return <div>...
Фрагменты
 кода
 с
 ReactJS
var artworkList = _.map(
this.props.artworks, function (item) {
return <li key={ item.id...
Фрагменты
 кода
 с
 ReactJS
var ArtworksPage = React.createClass({
render: function () {
var artworkList = _.map(
th...
Фрагменты
 кода
 с
 ReactJS
var ArtworksPage = React.createClass({
render: function () {
return <div>
<h1>Artwork li...
Структура
 SPA
 на
 React
• Автогенерация
 моделей
 на
 фронтенде
 
• Сериализация
 произвольных
 данных,
 включая
 
двусторонние
...
Спасибо!
Q&A
Site:
 ponyorm.com
 
Twi•er:
 @ponyorm
 
Github:
 github.com/ponyorm/pony
Александр
 Козловский,	...
Чем
 PonyORM
 отличается
 от
 Django
 ORM
 
 
• Pony
 использует
 паттерн
 Iden~ty
 Map,
 Django
...
Чем
 PonyORM
 отличается
 от
 Django
 ORM
 
 
• Идентация
 SQL
 запросов
 в
 логе
 
• Атрибут
 л...
Чем
 PonyORM
 отличается
 от
 Django
 ORM
 
 
• Поддержка
 пула
 соединений
 
 
• Кеширование
 рез...
<body>
 
 
 
 
 <table>
 
 
 
 
 
 
 
 
 <caption>Products</caption>
 
 
 
 
 
...
Двунаправленный биндинг
Other Pony ORM features
• Identity Map
• Automatic query optimization
• N+1 Query Problem solution
• Optimistic transactio...
Django ORM
s1 = Student.objects.get(pk=123)
print s1.name, s1.group.id
s2 = Student.objects.get(pk=456)
print s2.name, s2....
Django ORM
s1 = Student.objects.get(pk=123)
print s1.name, s1.group.id
s2 = Student.objects.get(pk=456)
print s2.name, s2....
Django ORM
s1 = Student.objects.get(pk=123)
print s1.name, s1.group.id
s2 = Student.objects.get(pk=456)
print s2.name, s2....
Django ORM
s1 = Student.objects.get(pk=123)
print s1.name, s1.group.id
s2 = Student.objects.get(pk=456)
print s2.name, s2....
Django ORM
s1 = Student.objects.get(pk=123)
print s1.name, s1.group.id
s2 = Student.objects.get(pk=456)
print s2.name, s2....
Pony ORM
s1 = Student[123]
print s1.name, s1.group.id
s2 = Student[456]
print s2.name, s2.group.id
Pony ORM – seeds, IdentityMap
s1 = Student[123]
print s1.name, s1.group.id
s2 = Student[456]
print s2.name, s2.group.id
St...
Pony ORM – seeds, IdentityMap
s1 = Student[123]
print s1.name, s1.group.id
s2 = Student[456]
print s2.name, s2.group.id
St...
Pony ORM – seeds, IdentityMap
s1 = Student[123]
print s1.name, s1.group.id
s2 = Student[456]
print s2.name, s2.group.id
St...
Pony ORM – seeds, IdentityMap
s1 = Student[123]
print s1.name, s1.group.id
s2 = Student[456]
print s2.name, s2.group.id
St...
Pony ORM – seeds, IdentityMap
s1 = Student[123]
print s1.name, s1.group.id
s2 = Student[456]
print s2.name, s2.group.id
St...
Solution for the N+1 Query Problem
orders
 =
 select(o
 for
 o
 in
 Order
 if
 o.total_price
 >
 1000)...
Order 1
Order 3
Order 4
Order 7
Order 9
Customer 1
Customer 4
Customer 7
Solution for the N+1 Query Problem
orders = select(o for o in Order if o.price > 1000)
for o in orders:
print o.total_price, o.customer.name
!
SELECT c.id, c...
Order 1
Order 3
Order 4
Order 7
Order 9
Customer 1
Customer 4
Customer 7
Solution for the N+1 Query Problem
Transactions
!
def
 transfer_money(id1,
 id2,
 amount):
 
 
 
 
 account1
 =
 Account.objects.get(pk=...
@transaction.atomic
 
def
 transfer_money(id1,
 id2,
 amount):
 
 
 
 
 account1
 =
 Account.object...
@transaction.atomic
 
def
 transfer_money(id1,
 id2,
 amount):
 
 
 
 
 account1
 =
 Account.object...
@db_session
 
def
 transfer_money(id1,
 id2,
 amount):
 
 
 
 
 account1
 =
 Account.get_for_update...
@db_session
 
def
 transfer_money(id1,
 id2,
 amount):
 
 
 
 
 account1
 =
 Account[id1]
 
 	...
db_session
• Pony tracks which objects where changed
• No need to call save()
• Pony saves all updated objects in a single...
@db_session
 
def
 transfer_money(id1,
 id2,
 amount):
 
 
 
 
 account1
 =
 Account[id1]
 
 	...
UPDATE Account
SET amount = :new_value
WHERE id = :id
AND amount = :old_value
Optimistic Locking
Optimistic Locking
• Pony tracks which attributes were read and
updated
• If object wasn’t locked using the for_update
met...
of 104

Разработка одностраничных веб-приложений с использованием PonyORM и ReactJS - Алексей Малашкевич, Александр Козловский, Pony ORM

Выступление на PYCON RUSSIA 2015
Published on: Mar 4, 2016
Published in: Education      
Source: www.slideshare.net


Transcripts - Разработка одностраничных веб-приложений с использованием PonyORM и ReactJS - Алексей Малашкевич, Александр Козловский, Pony ORM

  • 1. Разработка     одностраничных  веб-­‐приложений     с  использованием     PonyORM  и  ReactJS Александр  Козловский,  Алексей  Малашкевич PyCon  Russia  2015
  • 2. Задача Разработать  Single  Page  Application Приложение  -­‐  библиотека  современного  искусства
  • 3. Задача Разработать  Single  Page  Application Приложение  -­‐  библиотека  современного  искусства Особенности  приложения:   1. Множество  связанных  объектов:   художники,  картины,  история  продаж,  выставки,   галлереи,  каталоги  и  т.д.
  • 4. Задача Разработать  Single  Page  Application Приложение  -­‐  библиотека  современного  искусства Особенности  приложения:   1. Множество  связанных  объектов:   художники,  картины,  история  продаж,  выставки,   галлереи,  каталоги  и  т.д.   2. Нужна  мобильная  версия  приложения  (native  app)
  • 5. Задача Разработать  Single  Page  Application Приложение  -­‐  библиотека  современного  искусства Особенности  приложения:   1. Множество  связанных  объектов:   художники,  картины,  история  продаж,  выставки,   галлереи,  каталоги  и  т.д.   2. Нужна  мобильная  версия  приложения  (native  app)   3. Разные  уровни  доступа:         обычные  пользователи,  платные  пользователи,         художники,  редакторы,  работники  галлерей  и  т.д.
  • 6. 1.  Фронтенд.  Современные  фреймворки:   • Backbone   • AngularJS   • KnockoutJS   • EmberJS   • ReactJS Выбор  технологий
  • 7. 1.  Фронтенд.  Современные  фреймворки:   • Backbone   • AngularJS   • KnockoutJS   • EmberJS   • ReactJS   ! ! 2.  Способ  передачи  данных  между  бэкендом  и   фронтендом   • REST   Выбор  технологий
  • 8. 1.  Фронтенд.  Современные  фреймворки:   • Backbone   • AngularJS   • KnockoutJS   • EmberJS   • ReactJS   ! ! 2.  Способ  передачи  данных  между  бэкендом  и   фронтендом   • REST   ! 3.  Бэкенд     ✓ Язык  -­‐  Python   ✓ База  данных  -­‐  PostgreSQL Выбор  технологий
  • 9. Проблемы   1. Дублирование  моделей  на  фронтенде  (уже  есть  на   бэкенде)   2. REST  может  генерировать  слишком  много  запросов   к  серверу   3. Нет  двунаправленных  связей  между  объектами  на   фронтенде  (а  на  бэкенде  есть)
  • 10. Решения   1. Генерировать  фронтенд   модели  автоматически  на   основе  моделей  на   бэкенде   ! 1. Дублирование  моделей   на  фронтенде  (уже  есть   на  бэкенде)   2. REST  может   генерировать  слишком   много  запросов  к   серверу   3. Нет  двунаправленных   связей  между   объектами  на   фронтенде  (а  на   бэкенде  есть)
  • 11. Решения   1. Генерировать  фронтенд   модели  автоматически  на   основе  моделей  на   бэкенде   ! 2  и  3.  Передавать  связанные   объекты  в  одном  пакете PonyJS 1. Дублирование  моделей   на  фронтенде  (уже  есть   на  бэкенде)   2. REST  может   генерировать  слишком   много  запросов  к   серверу   3. Нет  двунаправленных   связей  между   объектами  на   фронтенде  (а  на   бэкенде  есть)
  • 12. PonyJS   PonyJS  автоматически  генерирует  фронтенд  модели     на  основе  моделей  PonyORM  на  бэкенде   !
  • 13. Почему  PonyORM? o1 = Order.objects.get(pk=1) print(o1.total, o1.customer.id) o2 = Order.objects.get(pk=2) print(o2.total, o2.customer.id) Django  ORM: 1.  Iden~tyMap   Customer  1 Customer  1 Order  1 Order  2 Order  1 Order  2 Customer  1 o1 = Order[1] print(o1.total, o1.customer.id) o2 = Order[2] print(o2.total, o2.customer.id) PonyORM: Ac~ve  Record Iden~ty  Map
  • 14. session.query(Product).filter(          (Product.name.startswith('A')  &  (Product.image  ==  None))          |  (extract('year',  Product.created_at)  <  2015)) Почему  PonyORM? select(p  for  p  in  Product          if  p.name.startswith('A')  and  p.image  is  None          or  p.created_at.year  <  2015) Pony Product.objects.filter(          Q(name__startswith='A',  image__isnull=True)          |  Q(created_at__year__lt=2015)) Django SQLAlchemy 2.  Удобный  язык  запросов
  • 15. База данных Место  Pony  в  архитектуре  веб-­‐приложения PonyJS Data Access Business Logic Service API Приложение на сервере - сервис Data Structures (ViewModels) Business Logic Presentation Layer Браузер клиента PonyORM JSON JSON Статический HTML
  • 16. Общая  схема  взаимодействия   Браузер Сервер /artworks HTML /api/artworks JSON Первый   запрос AJAX  запросы
  • 17. class  Author(db.En~ty):          id  =  PrimaryKey(int,  auto=True)          name  =  Required(str)          artworks  =  Set("Artwork")   ! class  Artwork(db.En~ty):          id  =  PrimaryKey(int,  auto=True)          ~tle  =  Required(str)          image  =  Required(str)          author  =  Required(Author)   Описание  сущностей
  • 18. AJAX  запрос  с  фронтенда Браузер Сервер AJAX  запрос  /api/artworks JSON pony.load({         url:  '/api/artworks',     success:  funcDon(data)  {   ! !   }   })
  • 19. Обработчик  на  бэкенде Браузер Сервер JSON @app.route('/api/artworks')   @db_session   def  get_artworks():     artworks  =  Artwork.select().order_by(Artwork.id)[:3]     print(artworks)    #  [Artwork[1],  Artwork[2],  Artwork[3]]     return  artworks.to_json() AJAX  запрос  /api/artworks
  • 20. Получаем  список  artworks  на  фронтенде Браузер Сервер AJAX  запрос  /api/artworks JSON pony.load({         url:  '/api/artworks',     success:  funcDon(artworks)  {       console.log(artworks);     }   })
  • 21. Получили  на  фронтенде  тот  же  список
  • 22. Список  полноценных  объектов  сущностей
  • 23. Атрибуты  -­‐  это  getter&setters
  • 24. Переход  по  связям  объекта
  • 25. class  Author(db.En~ty):          id  =  PrimaryKey(int,  auto=True)          name  =  Required(str)          artworks  =  Set("Artwork")   ! class  Artwork(db.En~ty):          id  =  PrimaryKey(int,  auto=True)          ~tle  =  Required(str)          image  =  Required(str)          author  =  Required(Author)   У  объекта  Artwork  есть  связь  с  Author
  • 26. Добавляем  объекты  Author   Браузер Сервер JSON @app.route('/api/artworks')   @db_session   def  get_artworks():     artworks  =  Artwork.select().order_by(Artwork.id)[:3]     return  artworks.to_json(include=[Artwork.author]) AJAX  запрос  /api/artworks
  • 27. Получаем  список  artworks  на  фронтенде Браузер Сервер AJAX  запрос  /api/artworks JSON pony.load({         url:  '/api/artworks',     success:  funcDon(artworks)  {       console.log(artworks);     }   })
  • 28. Теперь  мы  видим  автора Как  же  он  был  передан?
  • 29. user data identity map schema Artwork[1] Artwork[2] Artwork[3] Author[1] Author[2] Формат  JSON  пакета
  • 30. Оба  artwork’а  ссылаются     на  один  и  тот  же  объект  Author
  • 31. user data identity map schema Artwork[1] Artwork[2] Artwork[3] Author[1] Author[2] Формат  JSON  пакета {            "data":  […],       !    "objects":  {…},   ! !    "schema":  [...]   ! }
  • 32. Пользовательские  данные "data":  [            {                    "pk":  1,                    "class":  "Artwork"            },            {                    "pk":  2,                    "class":  "Artwork"            },            {                    "pk":  3,                    "class":  "Artwork"            }   ]
  • 33. Iden~ty  Map  объектов "objects":  {          "Ar~st":  {                  "1":  {...},                  "2":  {...}          },          "Artwork":  {                  "1":  {...},                  "2":  {...},                  "3":  {...}          }   }
  • 34. Iden~ty  Map  объектов !        "Author":  {                  "1":  {                          "id":  1,                          "name":  "Author1"                  },                  "2":  {                          "id":  2,                          "name":  "Author2"                  }          },         !        "Artwork":  {                  "1":  {                          "id":  1,                          "~tle":  "Artwork1"                          "image":  "/images/1.jpg",                          "author":  1,                  },                  "2":  {                          "id":  2,                          ...                          "author":  1,                  },                  "3":  {                     "id":  3                     ...                          "author":  2,                  } "objects":  { }
  • 35. Схема  объектов "schema":  [     {                  "name":  "Author",                  "newA•rs":  [  …  ],                  "pkA•rs":  ["id"]     },     {                  "name":  "Artwork",                  "newA•rs":  [  …  ],                  "pkA•rs":  ["id"]     }   ]
  • 36. Схема  объектов !        {                  "name":  "Author",                  "newA•rs":  [                          {                                  "auto":  true,                                  "kind":  "PrimaryKey",                                  "name":  "id",                                  "type":  "int"                          },                          {                                  "kind":  "Required",                                  "name":  "name",                                  "type":  "unicode"                          },                          {                                  "kind":  "Set",                                  "name":  "artworks",                                  "reverse":  "author",                                  "type":  "Artwork"                          }                  ],                  "pkA•rs":  ["id"]          },         {                  "name":  "Artwork",                  "newA•rs":  [                          {                                  "auto":  true,                                  "kind":  "PrimaryKey",                                  "name":  "id",                                  "type":  "int"                          },                          {                                  "kind":  "Required",                                  "name":  "Dtle",                                  "type":  "unicode"                          },                          {                                  "kind":  "Required",                                  "name":  "image",                                  "type":  "unicode"                          },                          {                                  "kind":  "Required",                                  "name":  "author",                                  "reverse":  "artworks",                                  "type":  "Author"                          }                  ],                  "pkA•rs":  ["id"]          } "schema":  [ ]
  • 37. Подгрузка  дополнительных  объектов Браузер Сервер JSON AJAX  запрос  /api/artworks?page=2 полученные  объекты  будут  смержены       с  Identity  Map  на  фронтенде
  • 38. Изменим  атрибут  title
  • 39. Можно  сохранять  изменения Браузер Сервер JSON AJAX  запрос  /api/artworks изменить   создать   удалить   объекты JSON AJAX  запрос  /api/update
  • 40. Теперь  сохраним  изменения Браузер Сервер JSON AJAX  запрос  /api/artworks изменить   создать   удалить   объекты JSON AJAX  запрос  /api/update pony.save({         url:  '/api/update',     success:  funcDon(data)  {       …     }   })
  • 41. Передаваемый  JSON {      "data":  null,        "objects":  [        {            "class":  "Artwork",            "_cid_":  3,            "_status_":  "u",            "_pk_":  "1",            "~tle":  {                    "old":  "Artwork1",                    "new":  "New  Dtle"            }        }   ]}
  • 42. Обработчик  на  бэкенде Браузер Сервер JSON AJAX  запрос  /api/artworks изменить   создать   удалить   объекты JSON AJAX  запрос  /api/update @app.route('/update',  methods=['POST'])   @db_session   def  update():          diff  =  request.form['diff']          db.from_json(diff)          return  db.to_json({'status':  'ok'})
  • 43. А  как  же  права  доступа?
  • 44. Права  доступа • Проверяются  на  сервере  при  выполнении
 to_json()  и  from_json()   • Позволяют  спрятать  из  метаданных  классы  и   атрибуты  моделей,  которые
 не  имеет  права  видеть  текущий  пользователь
  • 45. Права  доступа • Гарантируют,  что  на  фронтенд  передаются
 только  объекты,  которые  можно  видеть
 (row-­‐level  permissions)  и  сериализуются
 только  атрибуты,  которые  можно  видеть   • При  создании  и  изменении  объектов
 проверяется  что  пользователь  имеет  права
 на  создание/изменение  конкретного
 экземпляра  объекта  и  конкретных  атрибутов
  • 46. Способы  задания  прав  доступа • На  уровне  классов   • На  уровне  объектов
 (row-­‐level  permissions)   Pony  предлагает  очень  удобный  декларативный   способ  задания
 row-­‐level  permissions
  • 47. На  основе  чего  задаются  права? • Группы  -­‐  характеризуют  текущего  пользователя.   Примеры:  admin,  editor,  visitor   • Метки  -­‐  описывают  свойства  отдельных   объектов.  Примеры:  public,  deleted   • Роли  -­‐  описывают  взаимоотношения   пользователя  и  конкретного  объекта.  Примеры:   owner,  creator,  moderator
  • 48. Пример  задания  прав  доступа with db.permissions_for(Artwork): allow('view’, group='anybody') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 49.  Порядок  вычисления  прав  доступа • Пони  запрашивает  группы  текущего  пользователя  и   кеширует  их   • Код  приложения  выбирает  объекты  запросом  и   вызывает  to_json()   • Пони  запрашивает  роли  текущего  пользователя   относительно  сериализуемых  объектов   • Код  приложения  вычисляет  роли  и  метки  и  сообщает   их  Пони   • Пони  проверяет  роли  на  соответствие  декларативно   заданным  правам  доступа
  • 50.  Получение  групп  текущего  пользователя @groups_getter(Author) def get_author_groups(author): return ['author'] ! @groups_getter(User) def get_user_groups(user): return [user.type]
  • 51. Использование  групп  для  задания  прав with db.permissions_for(Artwork): allow('view', group='anybody') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 52.  Получение  ролей  текущего  пользователя @roles_getter(Author, Artwork) def get_author_roles(author, artwork): if author is artwork.author: return ['owner']
  • 53. Использование  ролей  для  задания  прав with db.permissions_for(Artwork): allow('view', group='anybody') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 54. Проверка  роли  при  создании  объекта with db.permissions_for(Artwork): allow('view', group='anybody') allow('create, edit', role='owner') allow('edit, delete', group=‘admin') ! Юзер при создании объекта не сможет указать в поле artwork.author другого юзера!
  • 55.  Пример  использования  меток Допустим,  что  некоторые  картины   должны  быть  скрыты  от  обычных   пользователей  при  показе  на  сайте
  • 56. class  Author(db.En~ty):          id  =  PrimaryKey(int,  auto=True)          name  =  Required(str)          artworks  =  Set("Artwork")   ! class  Artwork(db.En~ty):          id  =  PrimaryKey(int,  auto=True)          ~tle  =  Required(str)          image  =  Required(str)          author  =  Required(Author)          hidden  =  Required(bool,  default=False)    Пример  использования  меток
  • 57.  Получение  меток  объекта @labels_getter(Artwork) def get_artwork_labels(artwork): if not artwork.hidden: return ['public']
  • 58. Пример  использования  меток with db.permissions_for(Artwork): allow('view', group='anybody', label='public') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 59.  Автоматические  фильтры  запросов Удобны,  если  мы  хотим  автоматически   добавлять  условие  ко  всем  запросам  для   определенного  класса
  • 60.  Автоматические  фильтры  запросов @default_filter(Artwork) def public(artwork): return not artwork.hidden
  • 61.  Автоматические  фильтры  запросов artworks = Artwork.select( lambda a: a.author.name == 'Gerhard Richter' ).order_by(lambda a: a.title)[:3]
  • 62.  Автоматические  фильтры  запросов artworks = Artwork.select( lambda a: a.author.name == 'Gerhard Richter' ).order_by(lambda a: a.title)[:3] ! SELECT "a"."id", "a"."title", "a"."image", "a"."hidden", "a"."author" FROM "Artwork" "a" INNER JOIN "Author" "author-1" ON "a"."author" = "author-1"."id" WHERE "author-1"."name" = 'Gerhard Richter' AND "a"."hidden" = 0 ORDER BY "a"."title" LIMIT 3
  • 63.    Отключение  автоматических  фильтров with default_filters_disabled: artworks = Artwork.select( lambda a: a.author.name == 'Gerhard Richter' ).order_by(lambda a: a.title)[:3]
  • 64.    Отключение  автоматических  фильтров with default_filters_disabled: artworks = Artwork.select( lambda a: a.author.name == 'Gerhard Richter' ).order_by(lambda a: a.title)[:3] ! SELECT "a"."id", "a"."title", "a"."image", "a"."hidden", "a"."author" FROM "Artwork" "a" INNER JOIN "Author" "author-1" ON "a"."author" = "author-1"."id" WHERE "author-1"."name" = 'Gerhard Richter' ORDER BY "a"."title" LIMIT 3
  • 65. Разные  права  для  разных  сайтов with db.permissions_for(Artwork): allow('view', group='anybody', label='public', site='public') allow('create, edit', role='owner', site='admin') allow('edit, delete', group='admin', site='admin')
  • 66. ReactJS • ReactJS  позволяет  строить  страницу  из   компонентов   • Компоненты  представляют  собой  слой   View   • Получают  свойства  (props)
 и  используют  их  при  рендеринге   • Объекты  PonyJS  можно  использовать
 в  качестве  значений  свойств  компонентов
  • 67. Пример  React-­‐компонента var ArtworkDescription = React.createClass({ render: function () { var artwork = this.props.artwork; return <div> <h2>{ artwork.title() }</h2> <img src={ artwork.image() } /> <p>{ artwork.author().name() }</p> </div> } });
  • 68. Пример  React-­‐компонента var ArtworksPage = React.createClass({ render: function () { var artworkList = … return <div> <h1>Artwork list</h1> <ul>{ artworkList }</ul> </div> } });
  • 69. Фрагменты  кода  с  ReactJS var artworkList = _.map( this.props.artworks, function (item) { return <li key={ item.id() }> <ArtworkDescription artwork={ item } /> </li> });
  • 70. Фрагменты  кода  с  ReactJS var ArtworksPage = React.createClass({ render: function () { var artworkList = _.map( this.props.artworks, function (item) { return <li key={ item.id() }> <ArtworkDescription artwork={ item } /> </li> }); return <div> <h1>Artwork list</h1> <ul>{ artworkList }</ul> </div> } });
  • 71. Фрагменты  кода  с  ReactJS var ArtworksPage = React.createClass({ render: function () { return <div> <h1>Artwork list</h1> <ul>{ _.map(this.props.artworks, function (item) { return <li key={ item.id() }> <ArtworkDescription artwork={ item } /> </li> }); }</ul> </div> } });
  • 72. Структура  SPA  на  React
  • 73. • Автогенерация  моделей  на  фронтенде   • Сериализация  произвольных  данных,  включая   двусторонние  связи  many-­‐to-­‐many  и  инстансы   сущностей   • Объекты  с  двунаправленными  связями  на   клиенте   • Двунаправленный  биндинг   • Передача  изменений  обратно  на  сервер   • Права  доступа Заключение.  Что  предлагает  PonyJS?
  • 74. Спасибо! Q&A Site:  ponyorm.com   Twi•er:  @ponyorm   Github:  github.com/ponyorm/pony Александр  Козловский,  Алексей  Малашкевич PyCon  Russia  2015
  • 75. Чем  PonyORM  отличается  от  Django  ORM     • Pony  использует  паттерн  Iden~ty  Map,  Django  -­‐   Ac~veRecord   • Позволяет  избегать  lost  updates   • Не  растрачивает  память   • В  связанных  объектах  на  других  концах  -­‐  не  копии,  а   единственный  экземляр  объекта   • Оптимизация  запросов  (подзапрос  в  LEFT  JOIN)   • Можно  работать  с  Пони  в  интерактивном  режиме   • Решение  проблемы  “N+1  запроса”   • Оптимистические  проверки   • Умный  реконнект   • Информативные  сообщения  об  ошибках
  • 76. Чем  PonyORM  отличается  от  Django  ORM     • Идентация  SQL  запросов  в  логе   • Атрибут  лифтинг  (multisets)   • Лаконичный  декларативный  язык  запросов   • Генераторы  и  лямбды   • Агрегирующие  функции  самого  языка  (sum,  min,  max)   • in  используется  для  подзапросов  и  LIKE,  транслируется   в  разный  SQL  в  зависимости  от  аргументов     • Raw  SQL   • Подстановка  параметров  в  запрос   • Множественное  наследование  сущностей   • Отсутствие  проблемы  “срез  базового  класса”
  • 77. Чем  PonyORM  отличается  от  Django  ORM     • Поддержка  пула  соединений     • Кеширование  результата  транслирования  запроса,   результата  запроса  (список  объектов),  и  сами  объекты.   Второй  селект  с  такими  же  параметрами  в  базу  не  пойдет   (если  мы  не  посылали  в  базу  апдейты,  если  посылали  -­‐   этот  кеш  сбросится)   • PonyJS
  • 78. <body>          <table>                  <caption>Products</caption>                  <tbody  data-­‐bind="foreach:  products">                          <tr>                                  <td><input  data-­‐bind="value:  name"></td>                                                  <ul  data-­‐bind="foreach:  categories">                                                  <li  data-­‐bind="text:  name"></li>                                          </ul>                                  </td>                          </tr>                  </tbody>          </table>   </body> Двунаправленный биндинг
  • 79. Двунаправленный биндинг
  • 80. Other Pony ORM features • Identity Map • Automatic query optimization • N+1 Query Problem solution • Optimistic transactions • ER Diagram Editor
  • 81. Django ORM s1 = Student.objects.get(pk=123) print s1.name, s1.group.id s2 = Student.objects.get(pk=456) print s2.name, s2.group.id ! • How many SQL queries will be executed? • How many objects will be created?
  • 82. Django ORM s1 = Student.objects.get(pk=123) print s1.name, s1.group.id s2 = Student.objects.get(pk=456) print s2.name, s2.group.id Student 123
  • 83. Django ORM s1 = Student.objects.get(pk=123) print s1.name, s1.group.id s2 = Student.objects.get(pk=456) print s2.name, s2.group.id Student 123 Group 1
  • 84. Django ORM s1 = Student.objects.get(pk=123) print s1.name, s1.group.id s2 = Student.objects.get(pk=456) print s2.name, s2.group.id Student 123 Student 456 Group 1
  • 85. Django ORM s1 = Student.objects.get(pk=123) print s1.name, s1.group.id s2 = Student.objects.get(pk=456) print s2.name, s2.group.id Student 123 Student 456 Group 1 Group 1
  • 86. Pony ORM s1 = Student[123] print s1.name, s1.group.id s2 = Student[456] print s2.name, s2.group.id
  • 87. Pony ORM – seeds, IdentityMap s1 = Student[123] print s1.name, s1.group.id s2 = Student[456] print s2.name, s2.group.id Student 123 Group 1
  • 88. Pony ORM – seeds, IdentityMap s1 = Student[123] print s1.name, s1.group.id s2 = Student[456] print s2.name, s2.group.id Student 123 Group 1 seed
  • 89. Pony ORM – seeds, IdentityMap s1 = Student[123] print s1.name, s1.group.id s2 = Student[456] print s2.name, s2.group.id Student 123 Group 1 seed
  • 90. Pony ORM – seeds, IdentityMap s1 = Student[123] print s1.name, s1.group.id s2 = Student[456] print s2.name, s2.group.id Student 123 Student 456 Group 1 seed
  • 91. Pony ORM – seeds, IdentityMap s1 = Student[123] print s1.name, s1.group.id s2 = Student[456] print s2.name, s2.group.id Student 123 Student 456 Group 1 seed
  • 92. Solution for the N+1 Query Problem orders  =  select(o  for  o  in  Order  if  o.total_price  >  1000)            .order_by(Order.id)[0:5]   for  o  in  orders:              print  o.total_price,  o.customer.name SELECT o.id, o.total_price, o.customer_id,... FROM "Order" o WHERE o.total_price > 1000 ORDER BY o.id LIMIT 5
  • 93. Order 1 Order 3 Order 4 Order 7 Order 9 Customer 1 Customer 4 Customer 7 Solution for the N+1 Query Problem
  • 94. orders = select(o for o in Order if o.price > 1000) for o in orders: print o.total_price, o.customer.name ! SELECT c.id, c.name, … FROM “Customer” c WHERE c.id IN (?, ?, ?) Solution for the N+1 Query Problem
  • 95. Order 1 Order 3 Order 4 Order 7 Order 9 Customer 1 Customer 4 Customer 7 Solution for the N+1 Query Problem
  • 96. Transactions ! def  transfer_money(id1,  id2,  amount):          account1  =  Account.objects.get(pk=id1)          if  account1.amount  <  amount:                  raise  ValueError('Not  enough  funds!')          account2  =  Account.object.get(pk=id2)          account1.amount  -­‐=  amount          account1.save()          account2.amount  +=  amount          account2.save() Django ORM
  • 97. @transaction.atomic   def  transfer_money(id1,  id2,  amount):          account1  =  Account.objects.get(pk=id1)          if  account1.amount  <  amount:                  raise  ValueError('Not  enough  funds!')          account2  =  Account.object.get(pk=id2)          account1.amount  -­‐=  amount          account1.save()          account2.amount  +=  amount          account2.save() Transactions Django ORM
  • 98. @transaction.atomic   def  transfer_money(id1,  id2,  amount):          account1  =  Account.objects.         select_for_update.get(pk=id1)          if  account1.amount  <  amount:                  raise  ValueError('Not  enough  funds!')          account2  =  Account.objects.         select_for_update.get(pk=id2)          account1.amount  -­‐=  amount          account1.save()          account2.amount  +=  amount          account2.save() Transactions Django ORM
  • 99. @db_session   def  transfer_money(id1,  id2,  amount):          account1  =  Account.get_for_update(id=id1)          if  account1.amount  <  amount:                  raise  ValueError('Not  enough  funds!')          account1.amount  -­‐=  amount          account2  =  Account.get_for_update(id=id2)          account2.amount  +=  amount Transactions Pony ORM
  • 100. @db_session   def  transfer_money(id1,  id2,  amount):          account1  =  Account[id1]          if  account1.amount  <  amount:                  raise  ValueError('Not  enough  funds!')          account1.amount  -­‐=  amount          Account[id2].amount  +=  amount Transactions Pony ORM
  • 101. db_session • Pony tracks which objects where changed • No need to call save() • Pony saves all updated objects in a single transaction automatically on leaving the db_session scope Transactions
  • 102. @db_session   def  transfer_money(id1,  id2,  amount):          account1  =  Account[id1]          if  account1.amount  <  amount:                  raise  ValueError('Not  enough  funds!')          account1.amount  -­‐=  amount          Account[id2].amount  +=  amount Transactions Pony ORM
  • 103. UPDATE Account SET amount = :new_value WHERE id = :id AND amount = :old_value Optimistic Locking
  • 104. Optimistic Locking • Pony tracks which attributes were read and updated • If object wasn’t locked using the for_update method, Pony uses the optimistic locking automatically