class AccountCacheGroup(orm.BaseModel): _kind = 135 _use_rule_engine = False keys = orm.SuperStringProperty( repeated=True, indexed=False) # stores 128bit md5 = can hold aprox 22k items def condition_taskqueue_or_admin(account, **kwargs): return account._is_taskqueue or account._root_admin _permissions = [ orm.ExecuteActionPermission('update', condition_taskqueue_or_admin) ] _actions = [ orm.Action( id='update', arguments={ 'ids': orm.SuperStringProperty(repeated=True), 'keys': orm.SuperTextProperty(), # compressed base64 encoded data 'delete': orm.SuperBooleanProperty(default=False) }, _plugin_groups=[ orm.PluginGroup(plugins=[Context()]), orm.PluginGroup(transactional=True, plugins=[AccountCacheGroupUpdate()]) ]), ]
class CountrySubdivision(orm.BaseModel): _kind = 13 parent_record = orm.SuperKeyProperty('1', kind='13', indexed=False) code = orm.SuperStringProperty('2', required=True, indexed=False) name = orm.SuperStringProperty('3', required=True) complete_name = orm.SuperTextProperty('4', required=True) type = orm.SuperStringProperty('5', required=True, indexed=False) active = orm.SuperBooleanProperty('6', required=True, default=True) def condition_not_guest(account, **kwargs): return not account._is_guest def condition_true(**kwargs): return True _permissions = [ orm.ExecuteActionPermission('search', condition_not_guest), orm.ReadFieldPermission(('parent_record', 'code', 'name', 'complete_name', 'type', 'active'), condition_true) ] _actions = [ orm.Action( id='search', arguments={ 'search': orm.SuperSearchProperty( default={'filters': [{'field': 'active', 'value': True, 'operator': '=='}], 'orders': [{'field': 'name', 'operator': 'asc'}]}, cfg={ 'search_arguments': {'kind': '13', 'options': {'limit': 100}}, 'ancestor_kind': '12', 'search_by_keys': True, 'filters': {'active': orm.SuperBooleanProperty(choices=(True,))}, 'indexes': [{'ancestor': True, 'filters': [('active', ['=='])], 'orders': [('name', ['asc', 'desc'])]}] } ) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), GetCache(cfg={'group': 'search_13', 'cache': ['auth']}), Read(), RulePrepare(), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities'}), Set(cfg={'d': {'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more'}}), CallbackExec() ] ) ] ) ]
class OrderCarrier(orm.BaseExpando): _kind = 123 _use_rule_engine = False description = orm.SuperTextProperty('1', required=True) reference = orm.SuperVirtualKeyProperty('2', kind='113', required=True, indexed=False) unit_price = orm.SuperDecimalProperty('3', required=True, indexed=False) taxes = orm.SuperLocalStructuredProperty(OrderTax, '4', repeated=True) subtotal = orm.SuperDecimalProperty('5', required=True, indexed=False) tax_subtotal = orm.SuperDecimalProperty('6', required=True, indexed=False) total = orm.SuperDecimalProperty('7', required=True, indexed=False) _default_indexed = False
class OrderProduct(orm.BaseExpando): _kind = 125 _use_rule_engine = False reference = orm.SuperVirtualKeyProperty( '1', kind='28', required=True, indexed=False) # the reference now has catalog->product key-path name = orm.SuperStringProperty('2', required=True, indexed=False) code = orm.SuperStringProperty('3', required=True, indexed=False) description = orm.SuperTextProperty('4', required=True) # Soft limit 64kb. unit_price = orm.SuperDecimalProperty('5', required=True, indexed=False) quantity = orm.SuperDecimalProperty('6', required=True, indexed=False) _default_indexed = False _expando_fields = { 'mass': orm.SuperDecimalProperty('7'), 'volume': orm.SuperDecimalProperty('8') }
class CatalogProduct(orm.BaseExpando): _kind = 28 _use_rule_engine = False name = orm.SuperStringProperty('1', required=True, indexed=False) code = orm.SuperStringProperty('2', required=True, indexed=False) description = orm.SuperTextProperty('3', required=True) # Soft limit 64kb. unit_price = orm.SuperDecimalProperty('4', required=True, indexed=False) availability = orm.SuperStringProperty( '5', required=True, indexed=False, choices=('in stock', 'available for order', 'out of stock', 'preorder')) image_width = orm.SuperIntegerProperty( '6', required=True, indexed=False ) # This could be removed if we stick to percentage possitioning, though that setup has not been tested! image_height = orm.SuperIntegerProperty( '7', required=True, indexed=False ) # This could be removed if we stick to percentage possitioning, though that setup has not been tested! position_top = orm.SuperFloatProperty( '8', required=True, indexed=False ) # This can represent percentage possition with three decimal precision (e.g. 99.999$)! position_left = orm.SuperFloatProperty( '9', required=True, indexed=False ) # This can represent percentage possition with three decimal precision (e.g. 99.999$)! _default_indexed = False _expando_fields = { 'mass': orm.SuperDecimalProperty('10'), 'volume': orm.SuperDecimalProperty('11') } def prepare(self, **kwargs): self.key = self.build_key(self.key_id_str, parent=kwargs.get('parent').parent())
class OrderMessage(orm.BaseExpando): _kind = 35 _use_rule_engine = False created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) agent = orm.SuperKeyProperty('2', kind='11', required=True, indexed=False) action = orm.SuperKeyProperty('3', kind='1', required=True) body = orm.SuperTextProperty('4', required=True) _default_indexed = True _virtual_fields = { '_agent': orm.SuperReferenceStructuredProperty( '11', callback=lambda self: self.agent.get_async(), format_callback=lambda self, value: value), '_action': orm.SuperComputedProperty(lambda self: self.action.id() if self.action else '') }
class Order(orm.BaseExpando): _kind = 34 ''' read: read_<order.account.id> search: search_34_<order.account.id> ''' DELETE_CACHE_POLICY = { 'group': [ lambda context: 'read_34_%s' % context._order.key._root._id_str, 'search_34_admin', 'search_34', lambda context: 'search_34_seller_%s' % context._order. seller_reference._root._id_str, lambda context: 'search_34_buyer_%s' % context._order.key._root._id_str ] } created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) state = orm.SuperStringProperty( '3', required=True, default='cart', choices=('cart', 'order')) # 'checkout', 'completed', 'canceled' date = orm.SuperDateTimeProperty('4', required=True) seller_reference = orm.SuperKeyProperty('5', kind='23', required=True) billing_address = orm.SuperLocalStructuredProperty('121', '6') shipping_address = orm.SuperLocalStructuredProperty('121', '7') currency = orm.SuperLocalStructuredProperty('17', '8', required=True) untaxed_amount = orm.SuperDecimalProperty('9', required=True, indexed=False) tax_amount = orm.SuperDecimalProperty('10', required=True, indexed=False) total_amount = orm.SuperDecimalProperty('11', required=True, indexed=False) payment_method = orm.SuperStringProperty( '12', required=False, choices=settings.AVAILABLE_PAYMENT_METHODS) payment_status = orm.SuperStringProperty('13', required=False, indexed=True) carrier = orm.SuperLocalStructuredProperty(OrderCarrier, '14') _default_indexed = False _virtual_fields = { '_seller': orm.SuperReferenceStructuredProperty('23', autoload=True, target_field='seller_reference'), '_tracker': orm.SuperReferenceProperty('136', autoload=True, callback=lambda self: self.get_tracker(), format_callback=lambda self, value: value), '_lines': orm.SuperRemoteStructuredProperty(OrderLine, repeated=True, search={ 'default': { 'filters': [], 'orders': [{ 'field': 'sequence', 'operator': 'asc' }] }, 'cfg': { 'indexes': [{ 'ancestor': True, 'filters': [], 'orders': [('sequence', ['asc'])] }], } }), '_messages': orm.SuperRemoteStructuredProperty(OrderMessage, repeated=True, search={ 'default': { 'filters': [], 'orders': [{ 'field': 'created', 'operator': 'desc' }] }, 'cfg': { 'indexes': [{ 'ancestor': True, 'filters': [], 'orders': [('created', ['desc'])] }], } }), '_seller_reference': orm.SuperComputedProperty(lambda self: self.seller_reference._structure if self.seller_reference else None), } def condition_taskqueue(account, **kwargs): return account._is_taskqueue def condition_cron(account, **kwargs): return account._is_cron def condition_not_guest_and_buyer_and_cart(account, entity, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state == "cart" def condition_root_or_buyer_or_seller(account, entity, **kwargs): if entity._original.seller_reference is None: return False return account._root_admin or (not account._is_guest and ( (entity._original.key_root == account.key) or (entity._original.seller_reference._root == account.key))) def condition_buyer_or_seller(account, entity, **kwargs): if entity._original.seller_reference is None: return False return not account._is_guest and ( (entity._original.key_root == account.key) or (entity._original.seller_reference._root == account.key)) def condition_search(account, action, entity, input, **kwargs): return action.key_id_str == "search" and (account._root_admin or ( (not account._is_guest and input["search"]["filters"][0]["field"] == "seller_reference" and input["search"]["filters"][0]["value"]._root == account.key) or (not account._is_guest and "ancestor" in input["search"] and input["search"]["ancestor"]._root == account.key))) def condition_pay(action, entity, **kwargs): return action.key_id_str == "pay" and entity._original.state == "cart" def condition_notify(action, entity, **kwargs): return action.key_id_str == "notify" and entity._original.state == "order" def condition_update_line(account, entity, action, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state == "cart" and action.key_id_str == "update_line" def condition_state(action, entity, **kwargs): return (action.key_id_str == "update_line" and entity.state == "cart") \ or (action.key_id_str == "pay" and entity.state == "order") def condition_update_and_view_order(account, entity, action, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state == "cart" and action.key_id_str in ("view_order", "update") def cache_group_search(context): key = 'search_34' _ancestor = context.input['search'].get('ancestor') filters = context.input['search'].get('filters') if context.account._root_admin: return '%s_admin' % key if filters and filters[0]['field'] == 'seller_reference' and filters[ 0]['value']._root == context.account.key: return '%s_seller_%s' % (key, context.account.key_id_str) if _ancestor and _ancestor._root == context.account.key: return '%s_buyer_%s' % (key, context.account.key_id_str) return key _permissions = [ # action.key_id_str not in ["search"] and... # Included payment_status in field permissions, will have to further analyse exclusion... orm.ExecuteActionPermission( ('update_line', 'view_order', 'update', 'delete', 'pay'), condition_not_guest_and_buyer_and_cart), orm.ExecuteActionPermission(('read'), condition_root_or_buyer_or_seller), orm.ExecuteActionPermission(('log_message'), condition_buyer_or_seller), orm.ExecuteActionPermission('search', condition_search), orm.ExecuteActionPermission('delete', condition_taskqueue), orm.ExecuteActionPermission(('cron', 'cron_notify'), condition_cron), orm.ExecuteActionPermission('see_messages', condition_buyer_or_seller), orm.ExecuteActionPermission('notify', condition_notify), orm.ReadFieldPermission( ('created', 'updated', 'state', 'date', 'seller_reference', 'billing_address', 'shipping_address', 'currency', 'untaxed_amount', 'tax_amount', 'total_amount', 'carrier', '_seller_reference', 'payment_status', 'payment_method', '_lines', '_messages.created', '_messages.agent', '_messages.action', '_messages.body', '_messages._action', '_tracker', '_seller.name', '_seller.logo', '_seller._stripe_publishable_key', '_seller._currency'), condition_root_or_buyer_or_seller), orm.WriteFieldPermission( ('date', 'seller_reference', 'currency', 'untaxed_amount', 'tax_amount', 'total_amount', 'payment_method', '_lines', 'carrier'), condition_update_line), orm.WriteFieldPermission('state', condition_state), orm.WriteFieldPermission(('payment_status', '_messages'), condition_pay), orm.WriteFieldPermission(('payment_status', '_messages'), condition_notify), orm.WriteFieldPermission('_messages', condition_buyer_or_seller), orm.WriteFieldPermission( ('date', 'shipping_address', 'billing_address', '_lines', 'carrier', 'untaxed_amount', 'tax_amount', 'total_amount', 'payment_method'), condition_update_and_view_order), orm.DenyWriteFieldPermission( ('_lines.taxes', '_lines.product.reference', '_lines.product.name', '_lines.product.code', '_lines.product.description', '_lines.product.unit_price', '_lines.product.mass', '_lines.product.volume'), condition_update_and_view_order) ] _actions = [ orm.Action(id='update_line', arguments={ 'buyer': orm.SuperKeyProperty(kind='19', required=True), 'quantity': orm.SuperDecimalProperty(required=True), 'product': orm.SuperKeyProperty(kind='28', required=True) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), OrderInit(), OrderPluginExec(cfg={'kinds': ['117']}), OrderProductSpecsFormat(), OrderUpdateLine(), OrderLineRemove(), OrderStockManagement(), OrderLineFormat(), OrderCarrierFormat(), OrderFormat(), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_order' }}) ]) ]), orm.Action( id='view_order', arguments={ 'buyer': orm.SuperKeyProperty(kind='19', required=True), 'seller': orm.SuperKeyProperty(kind='23', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache( cfg={ 'group': lambda context: 'read_34_%s' % context.input[ 'buyer']._root._id_str, 'cache': ['account'] }), OrderInit(), OrderPluginExec( cfg={'kinds': ['117']} ), # order currency must be available for everyone OrderProductSpecsFormat(), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_order' }}), CallbackExec() ]) ]), orm.Action(id='read', arguments={ 'key': orm.SuperKeyProperty(kind='34', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache( cfg={ 'group': lambda context: 'read_34_%s' % context. input['key']._root._id_str, 'cache': ['account'] }), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_order' }}), CallbackExec() ]) ]), orm.Action( id='update', arguments={ 'key': orm.SuperKeyProperty(kind='34', required=True), 'billing_address': orm.SuperLocalStructuredProperty('14'), 'shipping_address': orm.SuperLocalStructuredProperty('14'), 'carrier': orm.SuperVirtualKeyProperty(kind='113'), '_lines': orm.SuperLocalStructuredProperty(OrderLine, repeated=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read( cfg={ 'read': { '_lines': { 'config': { 'search': { 'options': { 'limit': 0 } } } } } }), Set(cfg={'d': { '_order._lines': 'input._lines' }}), OrderLineRemove(), OrderStockManagement(), OrderProductSpecsFormat(), OrderFormat( ), # Needed for Carrier. Alternative is to break down this plugin in two, pre-carrier & post-carrier one. OrderPluginExec(), OrderLineFormat(), OrderCarrierFormat(), OrderFormat(), RulePrepare(), RuleExec() ]), orm.PluginGroup(transactional=True, plugins=[ Write(), RulePrepare(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_order' }}) ]) ]), orm.Action( id='delete', arguments={'key': orm.SuperKeyProperty(kind='34', required=True)}, _plugin_groups=[ orm.PluginGroup( plugins=[Context( ), Read(), RulePrepare(), RuleExec()]), orm.PluginGroup(transactional=True, plugins=[ Delete(), DeleteCache(cfg=DELETE_CACHE_POLICY), RulePrepare(), Set(cfg={'d': { 'output.entity': '_order' }}) ]) ]), orm.Action( id='search', arguments={ 'search': orm.SuperSearchProperty( default={ 'filters': [], 'orders': [{ 'field': 'updated', 'operator': 'desc' }] }, cfg={ 'search_arguments': { 'kind': '34', 'options': { 'limit': settings.SEARCH_PAGE } }, 'ancestor_kind': '19', 'search_by_keys': True, 'filters': { 'name': orm.SuperStringProperty(), 'key': orm.SuperVirtualKeyProperty(kind='34', searchable=False), 'state': orm.SuperStringProperty(repeated=True, choices=('cart', 'order')), 'seller_reference': orm.SuperKeyProperty(kind='23', searchable=False) }, 'indexes': [{ 'orders': [('updated', ['asc', 'desc'])] }, { 'orders': [('created', ['asc', 'desc'])] }, { 'filters': [('key', ['=='])] }, { 'filters': [('state', ['IN'])], 'orders': [('updated', ['asc', 'desc'])] }, { 'ancestor': True, 'filters': [('state', ['IN'])], 'orders': [('updated', ['desc'])] }, { 'filters': [('seller_reference', ['=='])], 'orders': [('updated', ['desc'])] }] }) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache(cfg={ 'group': cache_group_search, 'cache': ['account'] }), Read(), RulePrepare(), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities'}), Set( cfg={ 'd': { 'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more' } }), CallbackExec() ]) ]), orm.Action( id='cron', arguments={}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(), RuleExec(), OrderCronDelete( cfg={ 'page': 100, 'cart_life': settings.ORDER_CART_LIFE, 'unpaid_order_life': settings.ORDER_UNPAID_LIFE }), CallbackExec() ]) ]), orm.Action( id='notify', skip_csrf=True, arguments={ 'payment_method': orm.SuperStringProperty( required=True, choices=settings.AVAILABLE_PAYMENT_METHODS), 'request': orm.SuperPickleProperty(), }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), OrderNotify(cfg={ 'options': { 'paypal': { 'webscr': settings.PAYPAL_WEBSCR } } }), OrderSetMessage( cfg={ 'expando_fields': 'new_message_fields', 'expando_values': 'new_message' }), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), RulePrepare(), Set(cfg={'d': { 'output.entity': '_order' }}), # both seller and buyer must get the message Notify( cfg={ 's': { 'sender': settings.NOTIFY_EMAIL, 'for_seller': False, 'subject': notifications.ORDER_NOTIFY_SUBJECT, 'body': notifications.ORDER_NOTIFY_BODY }, 'd': { 'recipient': '_order.buyer_email' } }), Notify( cfg={ 's': { 'sender': settings.NOTIFY_EMAIL, 'for_seller': True, 'subject': notifications.ORDER_NOTIFY_SUBJECT, 'body': notifications.ORDER_NOTIFY_BODY }, 'd': { 'recipient': '_order.seller_email' } }), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='pay', arguments={ 'key': orm.SuperKeyProperty(kind='34', required=True), 'token': orm.SuperStringProperty(required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read( cfg={ 'read': { '_lines': { 'config': { 'search': { 'options': { 'limit': 0 } } } } } }) ]), orm.PluginGroup( transactional=True, plugins=[ # Transaction failures can still cause payment charges to succeed. # Isolate as little plugins as possible in transaction to minimize transaction failures. # We can also implement a two step payment to make the payment more robust. OrderPay(), OrderSetMessage( cfg={ 'expando_fields': 'new_message_fields', 'expando_values': 'new_message' }), RulePrepare(), RuleExec(), Write(), RulePrepare(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_order' }}) ]), orm.PluginGroup(plugins=[ # both seller and buyer must get the message Notify( cfg={ 's': { 'sender': settings.NOTIFY_EMAIL, 'for_seller': False, 'subject': notifications.ORDER_NOTIFY_SUBJECT, 'body': notifications.ORDER_NOTIFY_BODY }, 'd': { 'recipient': '_order.buyer_email' } }), Notify( cfg={ 's': { 'sender': settings.NOTIFY_EMAIL, 'for_seller': True, 'subject': notifications.ORDER_NOTIFY_SUBJECT, 'body': notifications.ORDER_NOTIFY_BODY }, 'd': { 'recipient': '_order.seller_email' } }) ]) ]), orm.Action( id='see_messages', arguments={'key': orm.SuperKeyProperty(kind='34', required=True)}, _plugin_groups=[ orm.PluginGroup( plugins=[Context( ), Read(), RulePrepare(), RuleExec()]), orm.PluginGroup(transactional=True, plugins=[ OrderNotifyTrackerSeen(), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='log_message', arguments={ 'key': orm.SuperKeyProperty(kind='34', required=True), 'message': orm.SuperTextProperty(required=True, max_size=settings.MAX_MESSAGE_SIZE) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), OrderSetMessage(), RulePrepare(), RuleExec() ]), orm.PluginGroup(transactional=True, plugins=[ Write(), Set(cfg={'d': { 'output.entity': '_order' }}), OrderNotifyTrackerSet( cfg=settings.ORDER_CRON_NOTIFY_TIMER), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='cron_notify', arguments={}, _plugin_groups=[ orm.PluginGroup( plugins=[Context( ), Read(), RulePrepare(), RuleExec()]), orm.PluginGroup( transactional=False, plugins=[ # This plugin isolates its parts in transaction, so the group is wrapped in transaction. OrderCronNotify( cfg={ 's': { 'sender': settings.NOTIFY_EMAIL, 'subject': notifications.ORDER_LOG_MESSAGE_SUBJECT, 'body': notifications.ORDER_LOG_MESSAGE_BODY }, 'hours': settings.ORDER_CRON_NOTIFY_TIMER['hours'], 'minutes': settings.ORDER_CRON_NOTIFY_TIMER['minutes'], 'seconds': settings.ORDER_CRON_NOTIFY_TIMER['seconds'] }), CallbackExec() ]) ]) ] @property def buyer_email(self): account = self.root_entity account.read() return account._primary_email @property def seller_email(self): account = self.seller_reference._root.entity account.read() return account._primary_email @property def seller_and_buyer_emails(self): emails = [] emails.append(self.seller_email) emails.append(self.buyer_email) return emails def get_tracker(self): tracker = None if self.key: tracker = OrderNotifyTracker.build_key(self.key.urlsafe()).get() return tracker
class Catalog(orm.BaseExpando): _kind = 31 DELETE_CACHE_POLICY = { # only delete public cache when user saves published or indexed catalog 'satisfy': [(['search_31'], lambda context, group_id: True if (context._catalog.state == 'indexed' or (context._catalog.state != 'indexed' and (hasattr(context, 'catalog_original_state') and context. catalog_original_state == 'indexed'))) else False)], 'group': [ 'search_31', 'search_31_admin', lambda context: 'read_31_%s' % context._catalog.key._id_str, lambda context: 'search_31_%s' % context._catalog.key._root._id_str ] } created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) name = orm.SuperStringProperty('3', required=True) published_date = orm.SuperDateTimeProperty('4', required=False) discontinued_date = orm.SuperDateTimeProperty('5', required=False) state = orm.SuperStringProperty('6', required=True, default='draft', choices=('draft', 'published', 'indexed', 'discontinued')) _default_indexed = False _expando_fields = { 'cover': orm.SuperImageLocalStructuredProperty(CatalogImage, '7', process_config={ 'copy': True, 'copy_name': 'cover', 'transform': True, 'width': 240, 'height': 312, 'crop_to_fit': True }) } _virtual_fields = { '_images': orm.SuperImageRemoteStructuredProperty(CatalogImage, repeated=True, search={ 'default': { 'filters': [], 'orders': [{ 'field': 'sequence', 'operator': 'desc' }] }, 'cfg': { 'indexes': [{ 'ancestor': True, 'filters': [], 'orders': [('sequence', ['desc'])] }], } }), '_seller': orm.SuperReferenceStructuredProperty( '23', autoload=True, callback=lambda self: self.key.parent().get_async()) } def condition_not_guest(account, **kwargs): return not account._is_guest def condition_not_guest_and_owner_or_root(account, entity, **kwargs): return not account._is_guest and ( entity._original.key_root == account.key or account._root_admin) def condition_search(account, entity, action, input, **kwargs): def valid_search(): if action.key_id == 'search': _ancestor = input['search'].get('ancestor') _filters = input['search'].get('filters') if _filters: field = _filters[0]['field'] op = _filters[0]['operator'] value = _filters[0]['value'] if field == 'state' and op == 'IN': if value == ['indexed']: # home page return True else: if _ancestor: if 'discontinued' not in value: # seller catalogs view if not account._is_guest and _ancestor._root == account.key: return True if value == ['published', 'indexed']: # seller profile view return True return False return account._root_admin or valid_search() def condition_published_or_indexed(entity, **kwargs): return entity._original.state in ("published", "indexed") def condition_update(account, entity, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and (entity._original.state in ("draft", "published", "indexed")) def condition_not_guest_and_owner_and_draft(account, entity, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state == "draft" def condition_deny_write_field_permission(account, entity, action, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state == "draft" and action.key_id_str == "update" def condition_not_guest_and_owner_and_published(account, entity, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state in ("published", "indexed") def condition_root(account, **kwargs): return account._root_admin def condition_taskqueue(account, **kwargs): return account._is_taskqueue def condition_cron(account, **kwargs): return account._is_cron def condition_true(**kwargs): return True def condition_false(**kwargs): return False def condition_write_images(account, entity, action, **kwargs): return not account._is_guest and entity._original.key_root == account.key \ and entity._original.state == "draft" \ and action.key_id_str \ in ("read", "catalog_upload_images", "prepare") def condition_write_state(entity, action, **kwargs): return (action.key_id_str == "create" and entity.state == "draft") \ or (action.key_id_str == "publish" and entity.state == "published") \ or (action.key_id_str == "sudo_discontinue" and entity.state == "discontinued") \ or (action.key_id_str == "discontinue" and entity.state == "discontinued") \ or (action.key_id_str == "sudo" and entity.state != "draft") def condition_write_discontinued_date(entity, action, **kwargs): return action.key_id_str in ("sudo_discontinue", "discontinue", "sudo") and entity.state == "discontinued" def condition_write_published_date(entity, action, **kwargs): return action.key_id_str == "sudo" and entity.state in ("published", "indexed") def condition_duplicate(action, **kwargs): return action.key_id_str in ("catalog_process_duplicate") def cache_read(context): if context.input[ 'key']._root == context.account.key or context.account._root_admin: return 'account' else: return None def cache_search(context): _ancestor = context.input['search'].get('ancestor') if context.account._root_admin or (_ancestor and _ancestor._root == context.account.key): return 'account' return None def cache_group_search(context): key = 'search_31' _ancestor = context.input['search'].get('ancestor') if context.account._root_admin: return '%s_admin' % key if _ancestor and _ancestor._root == context.account.key: return '%s_%s' % (key, context.account.key_id_str) return key _permissions = [ orm.ExecuteActionPermission('prepare', condition_not_guest), orm.ExecuteActionPermission('create', condition_not_guest_and_owner_or_root), orm.ExecuteActionPermission('search', condition_search), orm.ExecuteActionPermission('read', condition_published_or_indexed), orm.ExecuteActionPermission('update', condition_update), orm.ExecuteActionPermission( ('read', 'publish', 'catalog_upload_images'), condition_not_guest_and_owner_and_draft), orm.ExecuteActionPermission( ('discontinue', 'catalog_duplicate'), condition_not_guest_and_owner_and_published), orm.ExecuteActionPermission(('read', 'sudo'), condition_root), orm.ExecuteActionPermission('cron', condition_cron), orm.ExecuteActionPermission(('account_discontinue', 'sudo_discontinue', 'catalog_process_duplicate', 'delete'), condition_taskqueue), # field permissions orm.ReadFieldPermission( ('created', 'updated', 'name', 'published_date', 'discontinued_date', 'state', 'cover', '_images'), condition_not_guest_and_owner_or_root), orm.WriteFieldPermission(('name', 'published_date', 'discontinued_date', 'cover', '_images'), condition_not_guest_and_owner_and_draft), orm.DenyWriteFieldPermission( ('_images.image', '_images.content_type', '_images.size', '_images.gs_object_name', '_images.serving_url'), condition_deny_write_field_permission), orm.WriteFieldPermission(('_images'), condition_write_images), orm.WriteFieldPermission(('_images.products.availability', ), condition_not_guest_and_owner_and_published), orm.WriteFieldPermission('state', condition_write_state), orm.WriteFieldPermission('discontinued_date', condition_write_discontinued_date), orm.WriteFieldPermission('published_date', condition_write_published_date), orm.ReadFieldPermission( ('created', 'updated', 'name', 'published_date', 'discontinued_date', 'state', 'cover', '_images'), condition_published_or_indexed), orm.ReadFieldPermission( ('_seller.name', '_seller.logo', '_seller._currency'), condition_true), orm.WriteFieldPermission( ('created', 'updated', 'name', 'published_date', 'discontinued_date', 'state', 'cover', '_images'), condition_duplicate) ] _actions = [ orm.Action(id='prepare', arguments={ 'seller': orm.SuperKeyProperty(kind='23', required=True) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_catalog' }}) ]) ]), orm.Action(id='create', arguments={ 'seller': orm.SuperKeyProperty(kind='23', required=True), 'name': orm.SuperStringProperty(required=True) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 's': { '_catalog.state': 'draft' }, 'd': { '_catalog.name': 'input.name' } }), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_catalog' }}) ]) ]), orm.Action(id='read', arguments={ 'key': orm.SuperKeyProperty(kind='31', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache( cfg={ 'group': lambda context: 'read_31_%s' % context. input['key']._id_str, 'cache': [cache_read, 'all'] }), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_catalog' }}), CallbackExec() ]) ]), orm.Action(id='update', arguments={ 'key': orm.SuperKeyProperty(kind='31', required=True), 'name': orm.SuperStringProperty(required=True), '_images': orm.SuperImageRemoteStructuredProperty(CatalogImage, repeated=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 'd': { '_catalog.name': 'input.name', 'catalog_original_state': '_catalog._original.state', '_catalog._images': 'input._images' } }), CatalogProcessCoverSet(), CatalogProcessProducts(), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_catalog' }}) ]) ]), orm.Action(id='catalog_upload_images', arguments={ 'key': orm.SuperKeyProperty(kind='31', required=True), '_images': orm.SuperImageLocalStructuredProperty(CatalogImage, upload=True, repeated=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), UploadImages( cfg={ 'path': '_catalog._images', 'images_path': 'input._images' }), CatalogProcessCoverSet(), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_catalog' }}) ]) ]), orm.Action( id='delete', arguments={'key': orm.SuperKeyProperty(kind='31', required=True)}, _plugin_groups=[ orm.PluginGroup( plugins=[Context( ), Read(), RulePrepare(), RuleExec()]), orm.PluginGroup( transactional=True, plugins=[ Delete(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_catalog' }}) ]) ]), orm.Action( id='search', arguments={ 'search': orm.SuperSearchProperty( default={ 'filters': [], 'orders': [{ 'field': 'created', 'operator': 'desc' }] }, cfg={ 'search_arguments': { 'kind': '31', 'options': { 'limit': settings.SEARCH_PAGE } }, 'ancestor_kind': '23', 'search_by_keys': True, 'filters': { 'name': orm.SuperStringProperty(), 'key': orm.SuperVirtualKeyProperty(kind='31', searchable=False), 'state': orm.SuperStringProperty( repeated=True, choices=('published', 'indexed', 'discontinued', 'draft')) }, 'indexes': [{ 'ancestor': True, 'orders': [('created', ['desc'])] }, { 'ancestor': True, 'filters': [('state', ['IN'])], 'orders': [('created', ['desc']), ('key', ['desc'])] }, { 'ancestor': True, 'filters': [('state', ['IN'])], 'orders': [('published_date', ['desc']), ('key', ['desc'])] }, { 'orders': [('created', ['asc', 'desc'])] }, { 'orders': [('updated', ['asc', 'desc'])] }, { 'orders': [('published_date', ['asc', 'desc'])] }, { 'orders': [('discontinued_date', ['asc', 'desc'])] }, { 'filters': [('state', ['IN'])], 'orders': [('published_date', ['desc'])] }, { 'filters': [('key', ['=='])] }] }) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache( cfg={ 'group': cache_group_search, 'cache': ['admin', cache_search, 'all'] }), Read(), RulePrepare(cfg={'d': { 'input': 'input' }}), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities'}), Set( cfg={ 'd': { 'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more' } }), CallbackExec() ]) ]), orm.Action( id='publish', arguments={'key': orm.SuperKeyProperty(kind='31', required=True)}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 's': { '_catalog.state': 'published' }, 'd': { 'catalog_original_state': '_catalog._original.state' }, 'f': { '_catalog.published_date': lambda: datetime.datetime.now() } }), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), RulePrepare(), Set(cfg={'d': { 'output.entity': '_catalog' }}), # notify when user publishes catalog Notify( cfg={ 's': { 'subject': notifications.CATALOG_PUBLISH_SUBJECT, 'body': notifications.CATALOG_PUBLISH_BODY, 'sender': settings.NOTIFY_EMAIL }, 'd': { 'recipient': '_catalog.root_entity._primary_email' } }), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='sudo_discontinue', arguments={'key': orm.SuperKeyProperty(kind='31', required=True)}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 's': { '_catalog.state': 'discontinued' }, 'd': { 'catalog_original_state': '_catalog._original.state' }, 'f': { '_catalog.discontinued_date': lambda: datetime.datetime.now() } }), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), RulePrepare(), Set(cfg={'d': { 'output.entity': '_catalog' }}), # notify owner when catalog gets discontinued Notify( cfg={ 's': { 'subject': notifications.CATALOG_SUDO_SUBJECT, 'body': notifications. CATALOG_SUDO_DISCONTINUE_BODY, 'sender': settings.NOTIFY_EMAIL }, 'd': { 'recipient': '_catalog.root_entity._primary_email' } }), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='discontinue', arguments={'key': orm.SuperKeyProperty(kind='31', required=True)}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 's': { '_catalog.state': 'discontinued' }, 'd': { 'catalog_original_state': '_catalog._original.state' }, 'f': { '_catalog.discontinued_date': lambda: datetime.datetime.now() } }), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), RulePrepare(), Set(cfg={'d': { 'output.entity': '_catalog' }}), # notify owner when catalog gets discontinued Notify( cfg={ 's': { 'subject': notifications.CATALOG_DISCONTINUE_SUBJECT, 'body': notifications.CATALOG_DISCONTINUE_BODY, 'sender': settings.NOTIFY_EMAIL }, 'd': { 'recipient': '_catalog.root_entity._primary_email' } }), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='account_discontinue', arguments={ 'account': orm.SuperKeyProperty(kind='11', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[Context( ), Read(), RulePrepare(), RuleExec()]), orm.PluginGroup(transactional=True, plugins=[CatalogDiscontinue(), CallbackExec()]) ]), orm.Action( id='sudo', arguments={ 'key': orm.SuperKeyProperty(kind='31', required=True), 'state': orm.SuperStringProperty(required=True, choices=('published', 'indexed', 'discontinued')), 'message': orm.SuperTextProperty(required=True), 'note': orm.SuperTextProperty(required=True) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 'd': { '_catalog.state': 'input.state', 'catalog_original_state': '_catalog._original.state' }, 'f': { '_catalog.published_date': lambda: datetime.datetime.now(), '_catalog.discontinued_date': lambda: datetime.datetime.now() } } ), # ATM permissions handle if this field is writable. RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), RulePrepare(), Set(cfg={'d': { 'output.entity': '_catalog' }}), # use 1 notify plugin with dynamic email Notify( cfg={ 's': { 'subject': notifications.CATALOG_SUDO_SUBJECT, 'body': notifications.CATALOG_SUDO_BODY, 'sender': settings.NOTIFY_EMAIL }, 'd': { 'recipient': '_catalog.root_entity._primary_email' } }), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action( id='cron', arguments={}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(), RuleExec(), CatalogCronDelete( cfg={ 'page': 100, 'unpublished_life': settings.CATALOG_UNPUBLISHED_LIFE, 'discontinued_life': settings.CATALOG_DISCONTINUED_LIFE }), CallbackExec() ]) ]), orm.Action(id='catalog_duplicate', arguments={ 'key': orm.SuperKeyProperty(kind='31', required=True), 'channel': orm.SuperStringProperty(required=True) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_catalog' }}), CallbackExec(cfg=[('callback', { 'action_id': 'catalog_process_duplicate', 'action_model': '31' }, { 'key': '_catalog.key_urlsafe', 'channel': 'input.channel' }, None)]) ]) ]), orm.Action( id='catalog_process_duplicate', arguments={ 'key': orm.SuperKeyProperty(kind='31', required=True), 'channel': orm.SuperStringProperty(required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[Context( ), Read(), RulePrepare(), RuleExec()]), orm.PluginGroup( transactional=True, plugins=[ Duplicate(), Set( cfg={ 's': { '_catalog.state': 'draft' }, 'rm': ['_catalog.created'] }), Write(), # notify duplication process complete via channel Notify( cfg={ 's': { 'sender': settings.NOTIFY_EMAIL }, 'd': { 'recipient': 'input.channel', 'catalog_key': '_catalog.key_urlsafe' }, 'method': 'channel' }), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]) ] @classmethod def prepare_key(cls, input, **kwargs): return cls.build_key(None, parent=input.get('seller'))
class Account(orm.BaseExpando): _kind = 11 _use_record_engine = True ''' Cache: 11_<account.id> ''' READ_CACHE_POLICY = { 'group': lambda context: '11_%s' % context.account.key_id_str, 'cache': ['account'] } DELETE_CACHE_POLICY = { 'group': [ 'admin', lambda context: '11_%s' % context._account.key_id_str, lambda context: '11_%s' % context.account.key_id_str ] } created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) state = orm.SuperStringProperty('3', required=True, default='active', choices=('active', 'suspended')) identities = orm.SuperStructuredProperty( AccountIdentity, '4', repeated=True) # Soft limit 100 instances. sessions = orm.SuperLocalStructuredProperty( AccountSession, '5', repeated=True) # Soft limit 100 instances. _default_indexed = False _virtual_fields = { 'ip_address': orm.SuperComputedProperty(lambda self: tools.get_remote_addr()), '_primary_email': orm.SuperComputedProperty(lambda self: self.primary_email()), '_csrf': orm.SuperComputedProperty(lambda self: self.get_csrf()), '_records': orm.SuperRecordProperty('11') } def condition_guest_and_active(entity, **kwargs): return entity._is_guest or entity._original.state == "active" def condition_true(entity, **kwargs): return True def condition_not_guest_and_owner(account, entity, **kwargs): return not account._is_guest and account.key == entity._original.key def condition_not_guest(account, **kwargs): return not account._is_guest def condition_root(account, **kwargs): return account._root_admin def condition_sudo_action_and_root(account, action, **kwargs): return action.key_id_str == "sudo" and account._root_admin _permissions = [ orm.ExecuteActionPermission('login', condition_guest_and_active), orm.ExecuteActionPermission('current_account', condition_true), orm.ExecuteActionPermission(('read', 'update', 'logout'), condition_not_guest_and_owner), orm.ExecuteActionPermission(('blob_upload_url', 'create_channel'), condition_not_guest), orm.ExecuteActionPermission(('read', 'search', 'sudo'), condition_root), orm.ReadFieldPermission(('created', 'updated', 'state', 'identities', 'sessions', '_primary_email'), condition_not_guest_and_owner), orm.ReadFieldPermission( ('created', 'updated', 'state', 'identities', 'sessions', '_primary_email', 'ip_address', '_records'), condition_root), orm.WriteFieldPermission( ('state', 'identities', 'sessions', '_primary_email', '_records'), condition_not_guest_and_owner), orm.WriteFieldPermission(('state', 'sessions', '_records'), condition_sudo_action_and_root) ] _actions = [ orm.Action(id='login', skip_csrf=True, arguments={ 'login_method': orm.SuperStringProperty( required=True, choices=[ login_method['type'] for login_method in settings.LOGIN_METHODS ]), 'code': orm.SuperStringProperty(), 'error_message': orm.SuperStringProperty(), 'state': orm.SuperStringProperty(), 'error': orm.SuperStringProperty(), 'redirect_to': orm.SuperStringProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), AccountLoginInit( cfg={ 'methods': settings.LOGIN_METHODS, 'get_host_url': settings.get_host_url }) ]), orm.PluginGroup(transactional=True, plugins=[ AccountLoginWrite(), DeleteCache(cfg=DELETE_CACHE_POLICY) ]) ]), orm.Action(id='current_account', skip_csrf=True, arguments={'read_arguments': orm.SuperJsonProperty()}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(cfg={'source': 'account.key'}), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_account' }}), CallbackExec() ]) ]), orm.Action(id='read', arguments={ 'key': orm.SuperKeyProperty(kind='11', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache(cfg=READ_CACHE_POLICY), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': { 'output.entity': '_account' }}), CallbackExec() ]) ]), orm.Action( id='update', arguments={ 'key': orm.SuperKeyProperty(kind='11', required=True), 'primary_identity': orm.SuperStringProperty(), 'disassociate': orm.SuperStringProperty(repeated=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), AccountUpdateSet(), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), DeleteCache(cfg=DELETE_CACHE_POLICY), Set(cfg={'d': { 'output.entity': '_account' }}), CallbackExec(cfg=[('callback', { 'action_id': 'account_discontinue', 'action_model': '31' }, { 'account': '_account.key_urlsafe', 'account_state': '_account.state' }, lambda account, account_state, **kwargs: account_state == 'suspended')]) ]) ]), orm.Action( id='search', arguments={ 'search': orm.SuperSearchProperty( default={ 'filters': [], 'orders': [{ 'field': 'created', 'operator': 'desc' }] }, cfg={ 'search_arguments': { 'kind': '11', 'options': { 'limit': settings.SEARCH_PAGE } }, 'filters': { 'key': orm.SuperVirtualKeyProperty(kind='11', searchable=False), 'state': orm.SuperStringProperty(choices=('active', 'suspended')), 'identities.email': orm.SuperStringProperty(searchable=False) }, 'indexes': [{ 'orders': [('created', ['asc', 'desc'])] }, { 'orders': [('updated', ['asc', 'desc'])] }, { 'filters': [('key', ['=='])] }, { 'filters': [('identities.email', ['=='])] }, { 'filters': [('state', ['=='])], 'orders': [('created', ['asc', 'desc'])] }, { 'filters': [('state', ['=='])], 'orders': [('updated', ['asc', 'desc'])] }] }) }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), GetCache(cfg={ 'group': 'admin', 'cache': ['admin'] }), Read(), RulePrepare(), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities'}), Set( cfg={ 'd': { 'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more' } }) ]) ]), orm.Action( id='sudo', arguments={ 'key': orm.SuperKeyProperty(kind='11', required=True), 'state': orm.SuperStringProperty(required=True, choices=('active', 'suspended')), 'message': orm.SuperTextProperty(required=True), 'note': orm.SuperTextProperty() }, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set( cfg={ 'rm': ['_account.sessions'], 'd': { '_account.state': 'input.state' } }), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': { 'output.entity': '_account' }}), Notify( cfg={ 's': { 'subject': notifications.ACCOUNT_SUDO_SUBJECT, 'body': notifications.ACCOUNT_SUDO_BODY, 'sender': settings.NOTIFY_EMAIL }, 'd': { 'recipient': '_account._primary_email' } }), DeleteCache(cfg=DELETE_CACHE_POLICY), CallbackExec(cfg=[('callback', { 'action_id': 'account_discontinue', 'action_model': '31' }, { 'account': '_account.key_urlsafe', 'account_state': '_account.state' }, lambda account, account_state, **kwargs: account_state == 'suspended')]) ]) ]), orm.Action( id='logout', arguments={'key': orm.SuperKeyProperty(kind='11', required=True)}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), Set(cfg={'rm': ['_account.sessions']}), RulePrepare(), RuleExec() ]), orm.PluginGroup( transactional=True, plugins=[ Write( cfg={'dra': { 'ip_address': '_account.ip_address' }}), DeleteCache(cfg=DELETE_CACHE_POLICY), AccountLogoutOutput() ]) ]), orm.Action( id='blob_upload_url', arguments={'upload_url': orm.SuperStringProperty(required=True)}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(), RuleExec(), BlobURL(cfg={'bucket': settings.BUCKET_PATH}), Set(cfg={'d': { 'output.upload_url': '_blob_url' }}) ]) ]), orm.Action(id='create_channel', arguments={}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(), RuleExec(), CreateChannel(), Set(cfg={'d': { 'output.token': '_token' }}) ]) ]) ] def get_output(self): dic = super(Account, self).get_output() dic.update({ '_is_guest': self._is_guest, '_is_system': self._is_system, '_csrf': self._csrf, '_root_admin': self._root_admin }) location = self.current_location_data() if isinstance(location, dict): dic.update(location) return dic @property def _root_admin(self): return self._primary_email in settings.ROOT_ADMINS @property def _is_taskqueue(self): return tools.mem_temp_get('current_request_is_taskqueue') @property def _is_cron(self): return tools.mem_temp_get('current_request_is_cron') @property def _is_system(self): return self.key_id_str == 'system' @property def _is_guest(self): return self.key is None def primary_email(self): self.identities.read() if not self.identities.value: return None for identity in self.identities.value: if identity.primary: return identity.email def get_csrf(self): session = self.current_account_session() if not session: return tools.get_csrf_token() return hashlib.md5( '%s-%s' % (session.session_id, settings.CSRF_SALT)).hexdigest() @classmethod def current_account(cls): current_account = tools.mem_temp_get('current_account') if not current_account: current_account = cls() cls.set_current_account(current_account) return current_account @classmethod def system_account(cls): account_key = cls.build_key('system') account = account_key.get() if not account: identities = [ AccountIdentity(email='System', identity='1-0', primary=True) ] account = cls(key=account_key, state='active', identities=identities) account._use_rule_engine = False account.put() account._use_rule_engine = True return account @classmethod def current_account_session(cls): return tools.mem_temp_get('current_account_session') def session_by_id(self, session_id): for session in self.sessions.value: if session.session_id == session_id: return session return None def new_session(self): account = self session_ids = set() for session in account.sessions.value: if session.created < (datetime.datetime.now() - datetime.timedelta(days=10)): session._state = 'deleted' session_ids.add(session.session_id) while True: session_id = hashlib.md5(tools.random_chars(30)).hexdigest() if session_id not in session_ids: break session = AccountSession(session_id=session_id, ip_address=self.ip_address) account.sessions = [session] return session @classmethod def current_location_data(cls): return tools.mem_temp_get('current_request_location_data') @classmethod def set_location_data(cls, data): if data: if data.get('_country') and data.get('_country').lower() != 'zz': data['_country'] = orm.Key('12', data['_country'].lower()) if data.get('_region'): data['_region'] = orm.Key( '13', '%s-%s' % (data['_country']._id_str, data['_region'].lower()), parent=data['_country']) else: data['_region'] = None data['_country'] = None return tools.mem_temp_set('current_request_location_data', data) @classmethod def set_taskqueue(cls, flag): return tools.mem_temp_set('current_request_is_taskqueue', flag) @classmethod def set_cron(self, flag): return tools.mem_temp_set('current_request_is_cron', flag) @classmethod def set_current_account(cls, account, session=None): tools.mem_temp_set('current_account', account) tools.mem_temp_set('current_account_session', session) @classmethod def set_current_account_from_access_token(cls, access_token): try: account_key, session_id = access_token.split('|') except: return False # Fail silently if the authorization code is not set properly, or it is corrupted somehow. if not session_id: return False # Fail silently if the session id is not found in the split sequence. account_key = orm.Key(urlsafe=account_key) if account_key.kind() != cls.get_kind() or account_key.id( ) == 'system': return False # Fail silently if the kind is not valid account = account_key.get() if account: account.read() session = account.session_by_id(session_id) if session: cls.set_current_account(account, session) return account