Разработка	
  	
  
одностраничных	
  веб-­‐приложений	
  	
  
с	
  использованием	
  	
  
PonyORM	
  и	
  ReactJS
Александр	
  Козловский,	
  Алексей	
  Малашкевич
PyCon	
  Russia	
  2015
Задача
Разработать	
  Single	
  Page	
  Application
Приложение	
  -­‐	
  библиотека	
  современного	
  искусства
Задача
Разработать	
  Single	
  Page	
  Application
Приложение	
  -­‐	
  библиотека	
  современного	
  искусства
Особенности	
  приложения:	
  
1. Множество	
  связанных	
  объектов:	
  
художники,	
  картины,	
  история	
  продаж,	
  выставки,	
  
галлереи,	
  каталоги	
  и	
  т.д.
Задача
Разработать	
  Single	
  Page	
  Application
Приложение	
  -­‐	
  библиотека	
  современного	
  искусства
Особенности	
  приложения:	
  
1. Множество	
  связанных	
  объектов:	
  
художники,	
  картины,	
  история	
  продаж,	
  выставки,	
  
галлереи,	
  каталоги	
  и	
  т.д.	
  
2. Нужна	
  мобильная	
  версия	
  приложения	
  (native	
  app)
Задача
Разработать	
  Single	
  Page	
  Application
Приложение	
  -­‐	
  библиотека	
  современного	
  искусства
Особенности	
  приложения:	
  
1. Множество	
  связанных	
  объектов:	
  
художники,	
  картины,	
  история	
  продаж,	
  выставки,	
  
галлереи,	
  каталоги	
  и	
  т.д.	
  
2. Нужна	
  мобильная	
  версия	
  приложения	
  (native	
  app)	
  
3. Разные	
  уровни	
  доступа:	
  	
  
	
   	
   обычные	
  пользователи,	
  платные	
  пользователи,	
  	
  
	
   	
   художники,	
  редакторы,	
  работники	
  галлерей	
  и	
  т.д.
1.	
  Фронтенд.	
  Современные	
  фреймворки:	
  
• Backbone	
  
• AngularJS	
  
• KnockoutJS	
  
• EmberJS	
  
• ReactJS
Выбор	
  технологий
1.	
  Фронтенд.	
  Современные	
  фреймворки:	
  
• Backbone	
  
• AngularJS	
  
• KnockoutJS	
  
• EmberJS	
  
• ReactJS	
  
!
!
2.	
  Способ	
  передачи	
  данных	
  между	
  бэкендом	
  и	
  
фронтендом	
  
• REST	
  
Выбор	
  технологий
1.	
  Фронтенд.	
  Современные	
  фреймворки:	
  
• Backbone	
  
• AngularJS	
  
• KnockoutJS	
  
• EmberJS	
  
• ReactJS	
  
!
!
2.	
  Способ	
  передачи	
  данных	
  между	
  бэкендом	
  и	
  
фронтендом	
  
• REST	
  
!
3.	
  Бэкенд	
  	
  
✓ Язык	
  -­‐	
  Python	
  
✓ База	
  данных	
  -­‐	
  PostgreSQL
Выбор	
  технологий
Проблемы	
  
1. Дублирование	
  моделей	
  на	
  фронтенде	
  (уже	
  есть	
  на	
  
бэкенде)	
  
2. REST	
  может	
  генерировать	
  слишком	
  много	
  запросов	
  
к	
  серверу	
  
3. Нет	
  двунаправленных	
  связей	
  между	
  объектами	
  на	
  
фронтенде	
  (а	
  на	
  бэкенде	
  есть)
Решения	
  
1. Генерировать	
  фронтенд	
  
модели	
  автоматически	
  на	
  
основе	
  моделей	
  на	
  
бэкенде	
  
!
1. Дублирование	
  моделей	
  
на	
  фронтенде	
  (уже	
  есть	
  
на	
  бэкенде)	
  
2. REST	
  может	
  
генерировать	
  слишком	
  
много	
  запросов	
  к	
  
серверу	
  
3. Нет	
  двунаправленных	
  
связей	
  между	
  
объектами	
  на	
  
фронтенде	
  (а	
  на	
  
бэкенде	
  есть)
Решения	
  
1. Генерировать	
  фронтенд	
  
модели	
  автоматически	
  на	
  
основе	
  моделей	
  на	
  
бэкенде	
  
!
2	
  и	
  3.	
  Передавать	
  связанные	
  
объекты	
  в	
  одном	
  пакете
PonyJS
1. Дублирование	
  моделей	
  
на	
  фронтенде	
  (уже	
  есть	
  
на	
  бэкенде)	
  
2. REST	
  может	
  
генерировать	
  слишком	
  
много	
  запросов	
  к	
  
серверу	
  
3. Нет	
  двунаправленных	
  
связей	
  между	
  
объектами	
  на	
  
фронтенде	
  (а	
  на	
  
бэкенде	
  есть)
PonyJS	
  
PonyJS	
  автоматически	
  генерирует	
  фронтенд	
  модели	
  	
  
на	
  основе	
  моделей	
  PonyORM	
  на	
  бэкенде	
  
!
Почему	
  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
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.	
  Удобный	
  язык	
  запросов	
  
База данных
Место	
  Pony	
  в	
  архитектуре	
  веб-­‐приложения
PonyJS
Data Access
Business Logic
Service API
Приложение на сервере - сервис
Data Structures (ViewModels)
Business Logic
Presentation Layer
Браузер клиента
PonyORM
JSON JSON
Статический
HTML
Общая	
  схема	
  взаимодействия	
  
Браузер Сервер
/artworks
HTML
/api/artworks
JSON
Первый	
  
запрос
AJAX	
  запросы
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)	
  
Описание	
  сущностей
AJAX	
  запрос	
  с	
  фронтенда
Браузер Сервер
AJAX	
  запрос	
  /api/artworks
JSON
pony.load({	
  
	
  	
  	
   url:	
  '/api/artworks',	
  
	
   success:	
  funcDon(data)	
  {	
  
!
!
	
   }	
  
})
Обработчик	
  на	
  бэкенде
Браузер Сервер
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
Получаем	
  список	
  artworks	
  на	
  фронтенде
Браузер Сервер
AJAX	
  запрос	
  /api/artworks
JSON
pony.load({	
  
	
  	
  	
   url:	
  '/api/artworks',	
  
	
   success:	
  funcDon(artworks)	
  {	
  
	
   	
   console.log(artworks);	
  
	
   }	
  
})
Получили	
  на	
  фронтенде	
  тот	
  же	
  список
Список	
  полноценных	
  объектов	
  сущностей
Атрибуты	
  -­‐	
  это	
  getter&setters
Переход	
  по	
  связям	
  объекта
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
Добавляем	
  объекты	
  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
Получаем	
  список	
  artworks	
  на	
  фронтенде
Браузер Сервер
AJAX	
  запрос	
  /api/artworks
JSON
pony.load({	
  
	
  	
  	
   url:	
  '/api/artworks',	
  
	
   success:	
  funcDon(artworks)	
  {	
  
	
   	
   console.log(artworks);	
  
	
   }	
  
})
Теперь	
  мы	
  видим	
  автора
Как	
  же	
  он	
  был	
  передан?
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":	
  […],	
  
	
  	
  
!
	
  	
  "objects":	
  {…},	
  
!
!
	
  	
  "schema":	
  [...]	
  
!
}
Пользовательские	
  данные
"data":	
  [	
  
	
   	
  	
  	
  	
  {	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "pk":	
  1,	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "class":	
  "Artwork"	
  
	
   	
  	
  	
  	
  },	
  
	
   	
  	
  	
  	
  {	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "pk":	
  2,	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "class":	
  "Artwork"	
  
	
   	
  	
  	
  	
  },	
  
	
   	
  	
  	
  	
  {	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "pk":	
  3,	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "class":	
  "Artwork"	
  
	
   	
  	
  	
  	
  }	
  
]
Iden~ty	
  Map	
  объектов
"objects":	
  {	
  
	
  	
  	
  	
  "Ar~st":	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "1":	
  {...},	
  
	
  	
  	
  	
  	
  	
  	
  	
  "2":	
  {...}	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  "Artwork":	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "1":	
  {...},	
  
	
  	
  	
  	
  	
  	
  	
  	
  "2":	
  {...},	
  
	
  	
  	
  	
  	
  	
  	
  	
  "3":	
  {...}	
  
	
  	
  	
  	
  }	
  
}
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":	
  {
}	
  
Схема	
  объектов
"schema":	
  [	
  
	
   {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "name":	
  "Author",	
  
	
  	
  	
  	
  	
  	
  	
  	
  "newA•rs":	
  [	
  …	
  ],	
  
	
  	
  	
  	
  	
  	
  	
  	
  "pkA•rs":	
  ["id"]	
  
	
   },	
  
	
   {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "name":	
  "Artwork",	
  
	
  	
  	
  	
  	
  	
  	
  	
  "newA•rs":	
  [	
  …	
  ],	
  
	
  	
  	
  	
  	
  	
  	
  	
  "pkA•rs":	
  ["id"]	
  
	
   }	
  
]	
  
	
  	
  	
  
Схема	
  объектов
!
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "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":	
  [
]
Подгрузка	
  дополнительных	
  объектов
Браузер Сервер
JSON
AJAX	
  запрос	
  /api/artworks?page=2
полученные	
  объекты	
  будут	
  смержены	
  	
  	
  
с	
  Identity	
  Map	
  на	
  фронтенде
Изменим	
  атрибут	
  title
Можно	
  сохранять	
  изменения
Браузер Сервер
JSON
AJAX	
  запрос	
  /api/artworks
изменить	
  
создать	
  
удалить	
  
объекты
JSON
AJAX	
  запрос	
  /api/update
Теперь	
  сохраним	
  изменения
Браузер Сервер
JSON
AJAX	
  запрос	
  /api/artworks
изменить	
  
создать	
  
удалить	
  
объекты
JSON
AJAX	
  запрос	
  /api/update
pony.save({	
  
	
  	
  	
   url:	
  '/api/update',	
  
	
   success:	
  funcDon(data)	
  {	
  
	
   	
   …	
  
	
   }	
  
})
Передаваемый	
  JSON
{	
  
	
  	
  "data":	
  null,	
  	
  
	
  	
  "objects":	
  [	
  
	
   	
  	
  {	
  
	
   	
  	
  	
  	
  "class":	
  "Artwork",	
  
	
   	
  	
  	
  	
  "_cid_":	
  3,	
  
	
   	
  	
  	
  	
  "_status_":	
  "u",	
  
	
   	
  	
  	
  	
  "_pk_":	
  "1",	
  
	
   	
  	
  	
  	
  "~tle":	
  {	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "old":	
  "Artwork1",	
  
	
   	
  	
  	
  	
  	
  	
  	
  	
  "new":	
  "New	
  Dtle"	
  
	
   	
  	
  	
  	
  }	
  
	
   	
  	
  }	
  
]}
Обработчик	
  на	
  бэкенде
Браузер Сервер
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'})
А	
  как	
  же	
  права	
  доступа?
Права	
  доступа
• Проверяются	
  на	
  сервере	
  при	
  выполнении

to_json()	
  и	
  from_json()	
  
• Позволяют	
  спрятать	
  из	
  метаданных	
  классы	
  и	
  
атрибуты	
  моделей,	
  которые

не	
  имеет	
  права	
  видеть	
  текущий	
  пользователь
Права	
  доступа
• Гарантируют,	
  что	
  на	
  фронтенд	
  передаются

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

(row-­‐level	
  permissions)	
  и	
  сериализуются

только	
  атрибуты,	
  которые	
  можно	
  видеть	
  
• При	
  создании	
  и	
  изменении	
  объектов

проверяется	
  что	
  пользователь	
  имеет	
  права

на	
  создание/изменение	
  конкретного

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

(row-­‐level	
  permissions)	
  
Pony	
  предлагает	
  очень	
  удобный	
  декларативный	
  
способ	
  задания

row-­‐level	
  permissions
На	
  основе	
  чего	
  задаются	
  права?
• Группы	
  -­‐	
  характеризуют	
  текущего	
  пользователя.	
  
Примеры:	
  admin,	
  editor,	
  visitor	
  
• Метки	
  -­‐	
  описывают	
  свойства	
  отдельных	
  
объектов.	
  Примеры:	
  public,	
  deleted	
  
• Роли	
  -­‐	
  описывают	
  взаимоотношения	
  
пользователя	
  и	
  конкретного	
  объекта.	
  Примеры:	
  
owner,	
  creator,	
  moderator
Пример	
  задания	
  прав	
  доступа
with db.permissions_for(Artwork):
allow('view’, group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
 Порядок	
  вычисления	
  прав	
  доступа
• Пони	
  запрашивает	
  группы	
  текущего	
  пользователя	
  и	
  
кеширует	
  их	
  
• Код	
  приложения	
  выбирает	
  объекты	
  запросом	
  и	
  
вызывает	
  to_json()	
  
• Пони	
  запрашивает	
  роли	
  текущего	
  пользователя	
  
относительно	
  сериализуемых	
  объектов	
  
• Код	
  приложения	
  вычисляет	
  роли	
  и	
  метки	
  и	
  сообщает	
  
их	
  Пони	
  
• Пони	
  проверяет	
  роли	
  на	
  соответствие	
  декларативно	
  
заданным	
  правам	
  доступа
Использование	
  групп	
  для	
  задания	
  прав
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
 Получение	
  групп	
  текущего	
  пользователя
@groups_getter(Author)
def get_author_groups(author):
return ['author']
!
@groups_getter(User)
def get_user_groups(user):
return [user.type]
Использование	
  ролей	
  для	
  задания	
  прав
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
 Получение	
  ролей	
  текущего	
  пользователя
@roles_getter(Author, Artwork)
def get_author_roles(author, artwork):
if author is artwork.author:
return ['owner']
Проверка	
  роли	
  при	
  создании	
  объекта
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group=‘admin')
!
Юзер при создании объекта не
сможет указать в поле
artwork.author другого юзера!
 Пример	
  использования	
  меток
Допустим,	
  что	
  некоторые	
  картины	
  
должны	
  быть	
  скрыты	
  от	
  обычных	
  
пользователей	
  при	
  показе	
  на	
  сайте
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)	
  
	
  Пример	
  использования	
  меток
Пример	
  использования	
  меток
with db.permissions_for(Artwork):
allow('view', group='anybody',
label='public')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
 Получение	
  меток	
  объекта
@labels_getter(Artwork)
def get_artwork_labels(artwork):
if not artwork.hidden:
return ['public']
 Автоматические	
  фильтры	
  запросов
Удобны,	
  если	
  мы	
  хотим	
  автоматически	
  
добавлять	
  условие	
  ко	
  всем	
  запросам	
  для	
  
определенного	
  класса
 Автоматические	
  фильтры	
  запросов
@default_filter(Artwork)
def public(artwork):
return not artwork.hidden
 Автоматические	
  фильтры	
  запросов
artworks = Artwork.select(
lambda a: a.author.name == 'Gerhard Richter'
).order_by(lambda a: a.title)[:3]
 Автоматические	
  фильтры	
  запросов
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
 	
  Отключение	
  автоматических	
  фильтров
with default_filters_disabled:
artworks = Artwork.select(
lambda a: a.author.name == 'Gerhard Richter'
).order_by(lambda a: a.title)[:3]
 	
  Отключение	
  автоматических	
  фильтров
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
Разные	
  права	
  для	
  разных	
  сайтов
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')
ReactJS
• ReactJS	
  позволяет	
  строить	
  страницу	
  из	
  
компонентов	
  
• Компоненты	
  представляют	
  собой	
  слой	
  
View	
  
• Получают	
  свойства	
  (props)

и	
  используют	
  их	
  при	
  рендеринге	
  
• Объекты	
  PonyJS	
  можно	
  использовать

в	
  качестве	
  значений	
  свойств	
  компонентов
Пример	
  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>
}
});
Пример	
  React-­‐компонента
var ArtworksPage = React.createClass({
render: function () {
var artworkList = …
return <div>
<h1>Artwork list</h1>
<ul>{ artworkList }</ul>
</div>
}
});
Фрагменты	
  кода	
  с	
  ReactJS
var artworkList = _.map(
this.props.artworks, function (item) {
return <li key={ item.id() }>
<ArtworkDescription artwork={ item } />
</li>
});
Фрагменты	
  кода	
  с	
  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>
}
});
Фрагменты	
  кода	
  с	
  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>
}
});
Структура	
  SPA	
  на	
  React
• Автогенерация	
  моделей	
  на	
  фронтенде	
  
• Сериализация	
  произвольных	
  данных,	
  включая	
  
двусторонние	
  связи	
  many-­‐to-­‐many	
  
• Объекты	
  с	
  двунаправленными	
  связями	
  на	
  клиенте	
  
• Может	
  использоваться	
  с	
  любым	
  фронтенд-­‐
фреймворком	
  (React,	
  Angular,	
  Knockout	
  и	
  др.)	
  
• Формат	
  передаваемых	
  данных	
  универсален.	
  

Может	
  использоваться	
  с	
  другими	
  бэкендами	
  
• Двунаправленный	
  биндинг	
  
• Передача	
  изменений	
  обратно	
  на	
  сервер	
  
• Декларативные	
  права	
  доступа	
  на	
  уровне	
  объектов
Заключение.	
  Что	
  предлагает	
  PonyJS?
Спасибо!
Q&A
Site:	
  ponyorm.com	
  
Twi•er:	
  @ponyorm	
  
Github:	
  github.com/ponyorm/pony
Александр	
  Козловский,	
  Алексей	
  Малашкевич
PyCon	
  Russia	
  2015

Разработка Single Page Applications с использованием Pony ORM и React JS

  • 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.
  • 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.
  • 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.
    Использование  групп  для  задания  прав with db.permissions_for(Artwork): allow('view', group='anybody') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 51.
     Получение  групп  текущего  пользователя @groups_getter(Author) def get_author_groups(author): return ['author'] ! @groups_getter(User) def get_user_groups(user): return [user.type]
  • 52.
    Использование  ролей  для  задания  прав with db.permissions_for(Artwork): allow('view', group='anybody') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 53.
     Получение  ролей  текущего  пользователя @roles_getter(Author, Artwork) def get_author_roles(author, artwork): if author is artwork.author: return ['owner']
  • 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.
    Пример  использования  меток withdb.permissions_for(Artwork): allow('view', group='anybody', label='public') allow('create, edit', role='owner') allow('edit, delete', group='admin')
  • 58.
     Получение  меток  объекта @labels_getter(Artwork) defget_artwork_labels(artwork): if not artwork.hidden: return ['public']
  • 59.
     Автоматические  фильтры  запросов Удобны,  если  мы  хотим  автоматически   добавлять  условие  ко  всем  запросам  для   определенного  класса
  • 60.
  • 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.
  • 73.
    • Автогенерация  моделей  на  фронтенде   • Сериализация  произвольных  данных,  включая   двусторонние  связи  many-­‐to-­‐many   • Объекты  с  двунаправленными  связями  на  клиенте   • Может  использоваться  с  любым  фронтенд-­‐ фреймворком  (React,  Angular,  Knockout  и  др.)   • Формат  передаваемых  данных  универсален.  
 Может  использоваться  с  другими  бэкендами   • Двунаправленный  биндинг   • Передача  изменений  обратно  на  сервер   • Декларативные  права  доступа  на  уровне  объектов Заключение.  Что  предлагает  PonyJS?
  • 74.
    Спасибо! Q&A Site:  ponyorm.com   Twi•er:  @ponyorm   Github:  github.com/ponyorm/pony Александр  Козловский,  Алексей  Малашкевич PyCon  Russia  2015