class CategoryBalance(orm.BaseModel): _kind = 71 _use_rule_engine = False from_date = orm.SuperDateTimeProperty('1', required=True, indexed=False) to_date = orm.SuperDateTimeProperty('2', required=True, indexed=False) debit = orm.SuperDecimalProperty('3', required=True, indexed=False) credit = orm.SuperDecimalProperty('4', required=True, indexed=False) balance = orm.SuperDecimalProperty('5', required=True, indexed=False) uom = orm.SuperLocalStructuredProperty(uom.UOM, '6', required=True, indexed=False)
class Configuration(orm.BaseExpando): _kind = 57 _use_record_engine = False _use_rule_engine = False created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) configuration_input = orm.SuperPickleProperty('3', required=True, compressed=False, indexed=False) setup = orm.SuperStringProperty('4', required=True, indexed=False) state = orm.SuperStringProperty('5', required=True) next_operation = orm.SuperStringProperty('6', indexed=False) next_operation_input = orm.SuperPickleProperty('7', indexed=False) _default_indexed = False _global_role = GlobalRole(permissions=[ orm.ActionPermission('57', [ orm.Action.build_key('57', 'install'), orm.Action.build_key('57', 'cron_install') ], True, 'user._is_taskqueue') ]) _actions = [ orm.Action( key=orm.Action.build_key('57', 'install'), arguments={'key': orm.SuperKeyProperty(required=True, kind='57')}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), ConfigurationInstall() ]) ]), orm.Action(key=orm.Action.build_key('57', 'cron_install'), arguments={}, _plugin_groups=[ orm.PluginGroup(plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), ConfigurationCronInstall( cfg={'time': settings.SETUP_ELAPSED_TIME}) ]) ]) ]
class Session(orm.BaseModel): _kind = 70 _use_rule_engine = False created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True, indexed=False) session_id = orm.SuperStringProperty('2', required=True, indexed=False)
class Entry(orm.BaseExpando): '''Notes: In order to make proper instances of entries you must always either provide journal or _model_schema argument in constructor. Fields can only be properly loaded if are: - loaded from datastore - instanced with proper keyword argument Entry (journal or _model_schema) and Line (parent) ''' _kind = 50 _journal_fields_loaded = None # Used to flag if the journal fields were loaded. created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) journal = orm.SuperKeyProperty('3', kind=Journal, required=True) name = orm.SuperStringProperty('4', required=True) state = orm.SuperStringProperty('5', required=True) # @todo Bad thing about this prop being defined statically is that we can not have choices and default value, thus less abstraction! date = orm.SuperDateTimeProperty('6', required=True) _virtual_fields = { '_lines': orm.SuperRemoteStructuredProperty(Line, repeated=True) } _global_roles = {'system_sales_order': GlobalRole( permissions=[ orm.FieldPermission('50', ['created', 'updated', 'state'], False, None, 'True'), orm.FieldPermission('50', ['created', 'updated', 'name', 'state', 'entry_fields', 'line_fields', '_records', '_code', '_transaction_actions', '_transaction_plugin_groups'], False, False, 'entity._original.namespace_entity._original.state != "active"'), orm.FieldPermission('50', ['created', 'updated', 'name', 'state', 'entry_fields', 'line_fields', '_records', '_code'], False, None, 'entity._original.state != "draft"'), orm.FieldPermission('50', ['_transaction_actions', '_transaction_plugin_groups.name', '_transaction_plugin_groups.subscriptions', '_transaction_plugin_groups.active', '_transaction_plugin_groups.sequence', '_transaction_plugin_groups.transactional'], False, None, 'entity._is_system'), orm.FieldPermission('50', ['_transaction_plugin_groups.plugins'], False, None, 'entity._is_system and entity._original._transaction_plugin_groups.name != "User Plugins"'), # @todo Missing index between _transaction_plugin_groups and name! orm.FieldPermission('50', ['state'], True, None, '(action.key_id_str == "activate" and entity.state == "active") or (action.key_id_str == "decommission" and entity.state == "decommissioned")') ] )} @property def _global_role(self): return self._global_roles.get(self.journal._id_str) def __init__(self, *args, **kwargs): '''Caution! Making instances of Entry() inside a transaction may cause performing non-entity group queries (see journal_key.get() in add journal fields). As for get() itself it will use in-memory cache when it can. ''' journal = kwargs.get('journal') _model_schema = kwargs.pop('_model_schema', None) namespace = kwargs.get('namespace') if journal is None and (_model_schema is not None and namespace is not None): journal = Journal.build_key(_model_schema, namespace=namespace) kwargs['journal'] = journal if journal: self.add_journal_fields(journal) super(Entry, self).__init__(*args, **kwargs) def add_journal_fields(self, journal_key=None): if not self._journal_fields_loaded: # prevent from loading too many times if journal_key is None: journal_key = self.journal journal = journal_key.get() if journal is None: raise Exception('Cannot find journal with key %r.' % journal_key) self._clone_properties() for name, prop in journal.entry_fields.iteritems(): prop._code_name = name self._properties[prop._name] = prop self.add_output(name) self._journal_fields_loaded = True def get_kind(self): # @todo Do we have security breach here, without inclusion of namespace? return '%s_%s' % (self._get_kind(), self.journal.id()) @property def _actions(self): # @todo Cache if possible for performance gains! return Action.query(Action.active == True, ancestor=self.journal).fetch() def get_actions(self): return getattr(self, '_actions', []) def get_action(self, action): if isinstance(action, orm.Key): action_key = action else: try: action_key = orm.Key(urlsafe=action) except: action_key = Action.build_key(action, parent=self.journal) return action_key.get() def get_plugin_groups(self, action): return PluginGroup.query(PluginGroup.active == True, PluginGroup.subscriptions == action.key, ancestor=self.journal).order(PluginGroup.sequence).fetch() def get_fields(self): fields = super(Entry, self.__class__).get_fields() # Calling parent get_fields. for name, prop in self._properties.iteritems(): fields[prop._code_name] = prop return fields @classmethod def _from_pb(cls, pb, set_key=True, ent=None, key=None): '''Internal helper to create an entity from an EntityProto protobuf. First 10 lines of code are copied from original from_pb in order to mimic construction of entity instance based on its function args. The rest of the code bellow is used to forcefully attempt to attach properties from journal entry_fields. This is complicated because in order to properly deserialize from datastore, the deserialization process can only begin after we have successfully retrieved entry_fields from journal. ''' if not isinstance(pb, entity_pb.EntityProto): raise TypeError('pb must be an instance of EntityProto; received %r' % pb) if ent is None: ent = cls() # A key passed in overrides a key in the pb. if key is None and pb.key().path().element_size(): key = orm.Key(reference=pb.key()) # If set_key is not set, skip a trivial incomplete key. if key is not None and (set_key or key.id() or key.parent()): ent._key = key indexed_properties = pb.property_list() unindexed_properties = pb.raw_property_list() projection = [] all_props = [indexed_properties, unindexed_properties] added_fields = False for plist in all_props: for p in plist: # First find the journal. Then load all needed props and break the loop. journal_name = cls.journal._name if journal_name is None: journal_name = cls.journal._code_name if p.name() == journal_name: prop = ent._get_property_for(p, plist is indexed_properties) prop._deserialize(ent, p) # Calling deserialize on entities prop will unpack the property and set the value to the entity. ent.add_journal_fields() # Calling add_journal_fields without argument will use self.journal as journal key. added_fields = True break if not added_fields: raise Exception('Cannot proceed with loading of entry %s. Journal fields failed to set.' % ent) return super(Entry, cls)._from_pb(pb, set_key, ent, ent.key) # Calling parent from_pb to attempt to mantain compatibility with possible NDB upgrades? @classmethod def get_meta(cls): '''This function returns dictionary of meta data (not stored or dynamically generated data) of the model. The returned dictionary can be transalted into other understandable code to clients (e.g. JSON). ''' dic = {} dic['_actions'] = getattr(cls, '_actions', []) dic.update(super(Entry, cls).get_fields()) return dic
class Journal(orm.BaseExpando): _kind = 49 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) state = orm.SuperStringProperty('4', required=True, default='draft', choices=['draft', 'active', 'decommissioned']) entry_fields = orm.SuperPickleProperty('5', required=True, indexed=False, compressed=False) line_fields = orm.SuperPickleProperty('6', required=True, indexed=False, compressed=False) _default_indexed = False _virtual_fields = { '_records': orm.SuperRecordProperty('49'), '_code': orm.SuperComputedProperty(lambda self: self.key_id_str), '_transaction_actions': orm.SuperRemoteStructuredProperty(Action, repeated=True), '_transaction_plugin_groups': orm.SuperRemoteStructuredProperty(PluginGroup, repeated=True) } _global_role = GlobalRole( permissions=[ orm.ActionPermission('49', [orm.Action.build_key('49', 'prepare'), orm.Action.build_key('49', 'create'), orm.Action.build_key('49', 'read'), orm.Action.build_key('49', 'update'), orm.Action.build_key('49', 'delete'), orm.Action.build_key('49', 'search'), orm.Action.build_key('49', 'activate'), orm.Action.build_key('49', 'decommission')], False, 'entity._original.namespace_entity._original.state != "active"'), orm.ActionPermission('49', [orm.Action.build_key('49', 'delete')], False, 'entity._original.state != "draft"'), orm.ActionPermission('49', [orm.Action.build_key('49', 'activate')], False, 'entity._original.state == "active"'), orm.ActionPermission('49', [orm.Action.build_key('49', 'decommission')], False, 'entity._is_system or entity._original.state != "active"'), orm.FieldPermission('49', ['created', 'updated', 'state'], False, None, 'True'), orm.FieldPermission('49', ['created', 'updated', 'name', 'state', 'entry_fields', 'line_fields', '_records', '_code', '_transaction_actions', '_transaction_plugin_groups'], False, False, 'entity._original.namespace_entity._original.state != "active"'), orm.FieldPermission('49', ['created', 'updated', 'name', 'state', 'entry_fields', 'line_fields', '_records', '_code'], False, None, 'entity._original.state != "draft"'), orm.FieldPermission('49', ['_transaction_actions', '_transaction_plugin_groups.name', '_transaction_plugin_groups.subscriptions', '_transaction_plugin_groups.active', '_transaction_plugin_groups.sequence', '_transaction_plugin_groups.transactional'], False, None, 'entity._is_system'), orm.FieldPermission('49', ['_transaction_plugin_groups.plugins'], False, None, 'entity._is_system and entity._original._transaction_plugin_groups.name != "User Plugins"'), # @todo Missing index between _transaction_plugin_groups and name! orm.FieldPermission('49', ['state'], True, None, '(action.key_id_str == "activate" and entity.state == "active") or (action.key_id_str == "decommission" and entity.state == "decommissioned")') ] ) _actions = [ orm.Action( key=orm.Action.build_key('49', 'prepare'), arguments={ 'domain': orm.SuperKeyProperty(kind='6', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': {'output.entity': '_journal'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'create'), arguments={ 'domain': orm.SuperKeyProperty(kind='6', required=True), '_code': orm.SuperStringProperty(required=True, max_size=64), # Regarding max_size, take a look at the transaction.JournalUpdateRead() plugin! 'name': orm.SuperStringProperty(required=True), 'entry_fields': orm.SuperPropertyStorageProperty(required=True, cfg=JOURNAL_FIELDS), 'line_fields': orm.SuperPropertyStorageProperty(required=True, cfg=JOURNAL_FIELDS) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'s': {'_journal.state': 'draft'}, 'd': {'_journal.name': 'input.name', '_journal.entry_fields': 'input.entry_fields', '_journal.line_fields': 'input.line_fields'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': {'output.entity': '_journal'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'read'), arguments={ 'key': orm.SuperKeyProperty(kind='49', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': {'output.entity': '_journal'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'update'), arguments={ 'key': orm.SuperKeyProperty(kind='49', required=True), 'name': orm.SuperStringProperty(required=True), 'entry_fields': orm.SuperPropertyStorageProperty(required=True, cfg=JOURNAL_FIELDS), 'line_fields': orm.SuperPropertyStorageProperty(required=True, cfg=JOURNAL_FIELDS), '_transaction_actions': orm.SuperLocalStructuredProperty(Action, repeated=True), '_transaction_plugin_groups': orm.SuperLocalStructuredProperty(PluginGroup, repeated=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'d': {'_journal.name': 'input.name', '_journal.entry_fields': 'input.entry_fields', '_journal.line_fields': 'input.line_fields', '_journal._transaction_actions': 'input._transaction_actions', '_journal._transaction_plugin_groups': 'input._transaction_plugin_groups'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': {'output.entity': '_journal'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'delete'), arguments={ 'key': orm.SuperKeyProperty(kind='49', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Delete(), Set(cfg={'d': {'output.entity': '_journal'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'search'), arguments={ 'domain': orm.SuperKeyProperty(kind='6', required=True), 'search': orm.SuperSearchProperty( default={'filters': [], 'orders': [{'field': 'name', 'operator': 'asc'}]}, cfg={ 'search_arguments': {'kind': '49', 'options': {'limit': settings.SEARCH_PAGE}}, 'filters': {'name': orm.SuperStringProperty(), 'state': orm.SuperStringProperty(choices=[])}, 'indexes': [{'orders': [('name', ['asc', 'desc'])]}, {'orders': [('state', ['asc', 'desc'])]}, {'filters': [('name', ['==', '!='])], 'orders': [('name', ['asc', 'desc'])]}, {'filters': [('state', ['==', '!='])], 'orders': [('name', ['asc', 'desc'])]}, {'filters': [('state', ['==', '!=']), ('name', ['==', '!='])], 'orders': [('name', ['asc', 'desc'])]}] } ) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities'}), Set(cfg={'d': {'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'activate'), arguments={ 'key': orm.SuperKeyProperty(kind='49', required=True), 'message': orm.SuperTextProperty(required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'s': {'_journal.state': 'active'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message'}}), CallbackNotify(), CallbackExec() ] ), orm.PluginGroup( plugins=[ RulePrepare(), Set(cfg={'d': {'output.entity': '_journal'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('49', 'decommission'), arguments={ 'key': orm.SuperKeyProperty(kind='49', required=True), 'message': orm.SuperTextProperty(required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'s': {'_journal.state': 'decommissioned'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message'}}), CallbackNotify(), CallbackExec() ] ), orm.PluginGroup( plugins=[ RulePrepare(), Set(cfg={'d': {'output.entity': '_journal'}}) ] ) ] ) ] @classmethod def prepare_key(cls, input, **kwargs): code = input.get('_code') return cls.build_key(code, namespace=kwargs.get('namespace')) # @todo Possible prefix? @property def _is_system(self): return self.key_id_str.startswith('system_')
class Category(orm.BaseExpando): _kind = 47 created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) parent_record = orm.SuperKeyProperty('3', kind='47') name = orm.SuperStringProperty('4', required=True) complete_name = orm.SuperTextProperty('5', required=True) active = orm.SuperBooleanProperty('6', required=True, default=True) _default_indexed = False ###################################################### # Primitive example of real time balance calculator! # ###################################################### @classmethod def _get_children(cls, parent_records, children): entities = [] for parent_record in parent_records: entities.extend(cls.query(cls.parent_record == parent_record).fetch(keys_only=True)) if len(entities): children.extend(entities) cls._get_children(entities, children) @classmethod def _get_balance(cls, category): debit = 0 credit = 0 lines = Line.query(Line.categories.IN(category._children_records)).fetch() for line in lines: debit += line.debit credit += line.credit return (debit, credit, (debit - credit)) @classmethod def _post_get_hook(cls, key, future): # @todo Missing super extension! entity = future.get_result() if entity is not None and entity.key: entity._children_records = [entity.key] entity._get_children([entity.key], entity._children_records) entity._debit, entity._credit, entity._balance = entity._get_balance(entity) ################### # End of example! # ################### _expando_fields = { 'description': orm.SuperTextProperty('7'), 'balances': orm.SuperLocalStructuredProperty(CategoryBalance, '8', repeated=True) } _virtual_fields = { '_records': orm.SuperRecordProperty('47'), '_code': orm.SuperComputedProperty(lambda self: self.key_id_str) #'_debit': orm.SuperComputedProperty(lambda self: self._debit), #'_credit': orm.SuperComputedProperty(lambda self: self._credit), #'_balance': orm.SuperComputedProperty(lambda self: self._balance), } _global_role = GlobalRole( permissions=[ orm.ActionPermission('47', [orm.Action.build_key('47', 'prepare'), orm.Action.build_key('47', 'create'), orm.Action.build_key('47', 'read'), orm.Action.build_key('47', 'update'), orm.Action.build_key('47', 'delete'), orm.Action.build_key('47', 'search')], False, 'entity._original.namespace_entity._original.state != "active"'), orm.ActionPermission('47', [orm.Action.build_key('47', 'create'), orm.Action.build_key('47', 'update'), orm.Action.build_key('47', 'delete')], False, 'entity._is_system'), orm.ActionPermission('47', [orm.Action.build_key('47', 'delete')], False, 'entity._is_used'), orm.FieldPermission('47', ['created', 'updated'], False, None, 'True'), orm.FieldPermission('47', ['created', 'updated', 'parent_record', 'name', 'complete_name', 'active', 'description', 'balances', '_records', '_code'], False, False, 'entity._original.namespace_entity._original.state != "active"'), orm.FieldPermission('47', ['created', 'updated', 'parent_record', 'name', 'complete_name', 'active', 'description', 'balances', '_records', '_code'], False, None, 'entity._is_system') ] ) _actions = [ orm.Action( key=orm.Action.build_key('47', 'prepare'), arguments={ 'domain': orm.SuperKeyProperty(kind='6', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': {'output.entity': '_category'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('47', 'create'), arguments={ 'domain': orm.SuperKeyProperty(kind='6', required=True), '_code': orm.SuperStringProperty(required=True, max_size=64), # Regarding max_size, take a look at the transaction.CategoryUpdateRead() plugin! 'parent_record': orm.SuperKeyProperty(kind='47'), 'name': orm.SuperStringProperty(required=True), 'active': orm.SuperBooleanProperty(required=True, default=True), 'description': orm.SuperTextProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), CategoryUpdateSet(), # @todo Unless we decide to implement that complete_name handling property, this will stay. RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': {'output.entity': '_category'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('47', 'read'), arguments={ 'key': orm.SuperKeyProperty(kind='47', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': {'output.entity': '_category'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('47', 'update'), arguments={ 'key': orm.SuperKeyProperty(kind='47', required=True), 'parent_record': orm.SuperKeyProperty(kind='47'), 'name': orm.SuperStringProperty(required=True), 'active': orm.SuperBooleanProperty(required=True, default=True), 'description': orm.SuperTextProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), CategoryUpdateSet(), # @todo Unless we decide to implement that complete_name handling property, this will stay. RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': {'output.entity': '_category'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('47', 'delete'), arguments={ 'key': orm.SuperKeyProperty(kind='47', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Delete(), Set(cfg={'d': {'output.entity': '_category'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('47', 'search'), arguments={ 'domain': orm.SuperKeyProperty(kind='6', required=True), 'search': orm.SuperSearchProperty( default={'filters': [{'field': 'active', 'value': True, 'operator': '=='}], 'orders': [{'field': 'name', 'operator': 'asc'}]}, cfg={ 'search_by_keys': True, 'search_arguments': {'kind': '47', 'options': {'limit': settings.SEARCH_PAGE}}, 'filters': {'name': orm.SuperStringProperty(), 'active': orm.SuperBooleanProperty()}, 'indexes': [{'orders': [('name', ['asc', 'desc'])]}, {'orders': [('created', ['asc', 'desc'])]}, {'orders': [('updated', ['asc', 'desc'])]}, {'orders': [('active', ['asc', 'desc'])]}, {'filters': [('name', ['==', '!='])], 'orders': [('name', ['asc', 'desc'])]}, {'filters': [('active', ['=='])], 'orders': [('name', ['asc', 'desc'])]}, {'filters': [('active', ['==']), ('name', ['==', '!='])], 'orders': [('name', ['asc', 'desc'])]}] } ) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities'}), Set(cfg={'d': {'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more'}}) ] ) ] ) ] @classmethod def prepare_key(cls, input, **kwargs): code = input.get('_code') return cls.build_key(code, namespace=kwargs.get('namespace')) # @todo Possible prefix? @property def _is_system(self): return self.key_id_str.startswith('system_') @property def _is_used(self): if self.key.id() is None: return False category = self.query(self.__class__.parent_record == self.key).get() line = Line.query(Line.categories == self.key).get() return (category is not None) or (line is not None)
from app import orm, settings from app.models import uom from app.models.base import * from app.plugins.base import * from app.plugins.transaction import * defaults1 = () defaults2 = ('required',) JOURNAL_FIELDS = ((orm.SuperStringProperty(), defaults1, defaults2), (orm.SuperTextProperty(), defaults1, defaults2), (orm.SuperIntegerProperty(), defaults1, defaults2), (orm.SuperFloatProperty(), defaults1, defaults2), (orm.SuperDecimalProperty(), defaults1, defaults2), (orm.SuperBooleanProperty(), defaults1, defaults2), (orm.SuperJsonProperty(), defaults1, defaults2), (orm.SuperKeyProperty(), defaults1, defaults2), (orm.SuperDateTimeProperty(), defaults1, defaults2)) class Action(orm.Action): _kind = 84 _use_rule_engine = False arguments = orm.SuperPropertyStorageProperty('2', required=True, default={}, compressed=False, cfg=JOURNAL_FIELDS) @classmethod def build_key(cls, *args, **kwargs): new_args = [cls._get_kind()] new_args.extend(args) return orm.Key(*new_args, **kwargs)
class User(orm.BaseExpando): _kind = 0 _use_memcache = True created = orm.SuperDateTimeProperty('1', required=True, auto_now_add=True) updated = orm.SuperDateTimeProperty('2', required=True, auto_now=True) identities = orm.SuperStructuredProperty(Identity, '3', repeated=True) # Soft limit 100 instances. emails = orm.SuperStringProperty('4', repeated=True) # Soft limit 100 instances. state = orm.SuperStringProperty('5', required=True, choices=['active', 'suspended']) # @todo Shall we disable indexing here? sessions = orm.SuperLocalStructuredProperty(Session, '6', repeated=True) # Soft limit 100 instances. domains = orm.SuperKeyProperty('7', kind='6', repeated=True) # Soft limit 100 instances. @todo Shall we disable indexing here? _default_indexed = False _virtual_fields = { 'ip_address': orm.SuperComputedProperty(lambda self: os.environ.get('REMOTE_ADDR')), '_primary_email': orm.SuperComputedProperty(lambda self: self.primary_email()), '_records': orm.SuperRecordProperty('0') } _global_role = GlobalRole( permissions=[ orm.ActionPermission('0', orm.Action.build_key('0', 'login'), True, 'entity._is_guest or entity._original.state == "active"'), orm.ActionPermission('0', [orm.Action.build_key('0', 'read'), orm.Action.build_key('0', 'update'), orm.Action.build_key('0', 'logout'), orm.Action.build_key('0', 'read_domains')], True, 'not entity._is_guest and user.key == entity._original.key'), orm.FieldPermission('0', ['created', 'updated', 'state', 'domains'], False, True, 'not user._is_guest and user.key == entity._original.key'), orm.FieldPermission('0', ['identities', 'emails', 'sessions', '_primary_email'], True, True, 'not user._is_guest and user.key == entity._original.key'), # User is unit of administration, hence root admins need control over it! # Root admins can always: read user; search for users (exclusively); # read users history (exclusively); perform sudo operations (exclusively). orm.ActionPermission('0', [orm.Action.build_key('0', 'read'), orm.Action.build_key('0', 'search'), orm.Action.build_key('0', 'sudo')], True, 'user._root_admin'), orm.FieldPermission('0', ['created', 'updated', 'identities', 'emails', 'state', 'sessions', 'domains', 'ip_address', '_primary_email', '_records'], None, True, 'user._root_admin'), orm.FieldPermission('0', ['state'], True, None, 'action.key_id_str == "sudo" and user._root_admin') ] ) _actions = [ orm.Action( key=orm.Action.build_key('0', 'login'), arguments={ 'login_method': orm.SuperStringProperty(required=True, choices=settings.LOGIN_METHODS.keys()), 'code': orm.SuperStringProperty(), 'error': orm.SuperStringProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), UserLoginInit(cfg={'methods': settings.LOGIN_METHODS}) ] ), orm.PluginGroup( transactional=True, plugins=[ UserLoginWrite() ] ) ] ), orm.Action( key=orm.Action.build_key('0', 'read'), arguments={ 'key': orm.SuperKeyProperty(kind='0', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), Set(cfg={'d': {'output.entity': '_user'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('0', 'update'), arguments={ 'key': orm.SuperKeyProperty(kind='0', required=True), 'primary_email': orm.SuperStringProperty(), 'disassociate': orm.SuperStringProperty(repeated=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), UserUpdateSet(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': {'output.entity': '_user'}}), # CallbackNotify(), # CallbackExec() notify cannot work here ] ) ] ), orm.Action( key=orm.Action.build_key('0', 'search'), arguments={ 'search': orm.SuperSearchProperty( default={'filters': [], 'orders': [{'field': 'created', 'operator': 'desc'}]}, cfg={ 'search_arguments': {'kind': '0', 'options': {'limit': settings.SEARCH_PAGE}}, 'filters': {'emails': orm.SuperStringProperty(), 'state': orm.SuperStringProperty()}, 'indexes': [{'orders': [('emails', ['asc', 'desc'])]}, {'orders': [('created', ['asc', 'desc'])]}, {'orders': [('updated', ['asc', 'desc'])]}, {'filters': [('emails', ['==', '!='])], 'orders': [('created', ['asc', 'desc'])]}, {'filters': [('state', ['==', '!='])], 'orders': [('created', ['asc', 'desc'])]}] } ) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities', 'skip_user_roles': True}), Set(cfg={'d': {'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more'}}) ] ) ] ), # @todo Treba obratiti paznju na to da suspenzija usera ujedno znaci # i izuzimanje svih negativnih i neutralnih feedbackova koje je user ostavio dok je bio aktivan. orm.Action( key=orm.Action.build_key('0', 'sudo'), arguments={ 'key': orm.SuperKeyProperty(kind='0', required=True), 'state': orm.SuperStringProperty(required=True, choices=['active', 'suspended']), 'message': orm.SuperStringProperty(required=True), 'note': orm.SuperStringProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'d': {'_user.state': 'input.state'}, 's': {'_user.sessions': []}}), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message', 'note': 'input.note'}}), Set(cfg={'d': {'output.entity': '_user'}}), # CallbackNotify(), # CallbackExec() notify cannot work here ] ) ] ), orm.Action( key=orm.Action.build_key('0', 'logout'), arguments={ 'key': orm.SuperKeyProperty(kind='0', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'s': {'_user.sessions': []}}), RulePrepare(cfg={'skip_user_roles': True}), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'ip_address': '_user.ip_address'}}), UserLogoutOutput() ] ) ] ), # We need this action in order to properly prepare entities for client side access control! orm.Action( key=orm.Action.build_key('0', 'read_domains'), arguments={ 'key': orm.SuperKeyProperty(kind='0', required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), UserReadDomains() ] ) ] ) ] def get_output(self): dic = super(User, self).get_output() dic.update({'_csrf': self._csrf, # We will need the csrf but it has to be incorporated into security mechanism (http://en.wikipedia.org/wiki/Cross-site_request_forgery). '_is_guest': self._is_guest, '_root_admin': self._root_admin}) return dic @property def _root_admin(self): return self._primary_email in settings.ROOT_ADMINS @property def _is_taskqueue(self): return mem.temp_get('_current_request_is_taskqueue') @property def _is_cron(self): return mem.temp_get('_current_request_is_cron') def set_taskqueue(self, is_it): return mem.temp_set('_current_request_is_taskqueue', is_it) def set_cron(self, is_it): return mem.temp_set('_current_request_is_cron', is_it) def primary_email(self): if not self.identities.value: return None for identity in self.identities.value: if identity.primary == True: return identity.email return identity.email @property def _csrf(self): session = self.current_user_session() if not session: return None return hashlib.md5(session.session_id).hexdigest() @property def _is_guest(self): return self.key is None @classmethod def set_current_user(cls, user, session=None): mem.temp_set('_current_user', user) mem.temp_set('_current_user_session', session) @classmethod def current_user(cls): current_user = mem.temp_get('_current_user') if not current_user: current_user = cls() cls.set_current_user(current_user) return current_user @classmethod def get_system_user(cls): user_key = cls.build_key('system') user = user_key.get() if not user: identities = [Identity(email='System', identity='1-0', associated=True, primary=True)] user = cls(key=user_key, state='active', emails=['System'], identities=identities) user.put() return user @classmethod def current_user_session(cls): return mem.temp_get('_current_user_session') def session_by_id(self, session_id): for session in self.sessions.value: if session.session_id == session_id: return session return None @classmethod def set_current_user_from_auth_code(cls, auth_code): try: user_key, session_id = auth_code.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. user_key = orm.Key(urlsafe=user_key) if user_key.kind() != cls.get_kind(): return False # Fail silently if the kind is not valid user = user_key.get() if user and user.key_id != 'system': user.read() session = user.session_by_id(session_id) if session: cls.set_current_user(user, session) return user
class Domain(orm.BaseExpando): _kind = 6 _use_memcache = True 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) primary_contact = orm.SuperKeyProperty('4', kind='8', indexed=False) # This field is required, and is handeled in update action via argument! state = orm.SuperStringProperty('5', required=True, choices=['active', 'suspended', 'su_suspended']) logo = SuperImageLocalStructuredProperty(Image, '6', required=True) _default_indexed = False _virtual_fields = { '_primary_contact_email': orm.SuperReferenceProperty(target_field='primary_contact', format_callback=lambda self, value: value._primary_email), '_records': orm.SuperRecordProperty('6') } _global_role = GlobalRole( permissions=[ orm.ActionPermission('6', [orm.Action.build_key('6', 'prepare'), orm.Action.build_key('6', 'create')], True, 'not user._is_guest'), orm.ActionPermission('6', orm.Action.build_key('6', 'update'), False, 'entity._original.state != "active"'), orm.ActionPermission('6', orm.Action.build_key('6', 'suspend'), False, 'entity._original.state != "active"'), orm.ActionPermission('6', orm.Action.build_key('6', 'activate'), False, 'entity._original.state == "active" or entity._original.state == "su_suspended"'), orm.FieldPermission('6', ['created', 'updated', 'state'], False, None, 'True'), orm.FieldPermission('6', ['name', 'primary_contact', 'logo', '_records', '_primary_contact_email'], False, None, 'entity._original.state != "active"'), orm.FieldPermission('6', ['state'], True, None, '(action.key_id_str == "activate" and entity.state == "active") or (action.key_id_str == "suspend" and entity.state == "suspended")'), # Domain is unit of administration, hence root admins need control over it! # Root admins can always: read domain; search for domains (exclusively); # read domain history; perform sudo operations (exclusively); log messages; read _records.note field (exclusively). orm.ActionPermission('6', [orm.Action.build_key('6', 'read'), orm.Action.build_key('6', 'search'), orm.Action.build_key('6', 'sudo'), orm.Action.build_key('6', 'log_message')], True, 'user._root_admin'), orm.ActionPermission('6', [orm.Action.build_key('6', 'search'), orm.Action.build_key('6', 'sudo')], False, 'not user._root_admin'), orm.FieldPermission('6', ['created', 'updated', 'name', 'primary_contact', 'state', 'logo', '_records', '_primary_contact_email'], None, True, 'user._root_admin'), orm.FieldPermission('6', ['_records.note'], True, True, 'user._root_admin'), orm.FieldPermission('6', ['_records.note'], False, False, 'not user._root_admin'), orm.FieldPermission('6', ['state'], True, None, '(action.key_id_str == "sudo") and user._root_admin and (entity.state == "active" or entity.state == "su_suspended")'), orm.FieldPermission('6', ['created', 'updated', 'name', 'state', 'logo'], None, True, 'entity._original.state == "active"') ] ) _actions = [ orm.Action( key=orm.Action.build_key('6', 'prepare'), arguments={ 'upload_url': orm.SuperStringProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), BlobURL(cfg={'bucket': settings.BUCKET_PATH}), Set(cfg={'d': {'output.entity': '_domain', 'output.upload_url': '_blob_url'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'create'), arguments={ 'name': orm.SuperStringProperty(required=True), 'logo': SuperImageLocalStructuredProperty(Image, required=True, process_config={'measure': False, 'transform': True, 'width': 240, 'height': 100, 'crop_to_fit': True}) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ DomainCreateWrite(), Set(cfg={'d': {'output.entity': '_domain'}}), CallbackExec(cfg=[('callback', {'action_id': 'install', 'action_model': '57'}, {'key': '_config.key_urlsafe'})]) ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'read'), arguments={ 'key': orm.SuperKeyProperty(kind='6', required=True), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec(), Set(cfg={'d': {'output.entity': '_domain'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'update'), arguments={ 'key': orm.SuperKeyProperty(kind='6', required=True), 'name': orm.SuperStringProperty(required=True), 'primary_contact': orm.SuperKeyProperty(required=True, kind='8', validator=primary_contact_validator), 'logo': SuperImageLocalStructuredProperty(Image, process_config={'measure': False, 'transform': True, 'width': 240, 'height': 100, 'crop_to_fit': True}), 'read_arguments': orm.SuperJsonProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'d': {'_domain.name': 'input.name', '_domain.primary_contact': 'input.primary_contact', '_domain.logo': 'input.logo'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(), Set(cfg={'d': {'output.entity': '_domain'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'search'), arguments={ 'search': orm.SuperSearchProperty( default={'filters': [], 'orders': [{'field': 'created', 'operator': 'desc'}]}, cfg={ 'search_arguments': {'kind': '6', 'options': {'limit': settings.SEARCH_PAGE}}, 'filters': {'name': orm.SuperStringProperty(), 'state': orm.SuperStringProperty()}, 'indexes': [{'orders': [('name', ['asc', 'desc'])]}, {'orders': [('created', ['asc', 'desc'])]}, {'orders': [('updated', ['asc', 'desc'])]}, {'filters': [('name', ['==', '!='])], 'orders': [('created', ['asc', 'desc'])]}, {'filters': [('state', ['==', '!='])], 'orders': [('created', ['asc', 'desc'])]}] } ) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(cfg={'skip_user_roles': True}), RuleExec(), Search(), RulePrepare(cfg={'path': '_entities', 'skip_user_roles': True}), Set(cfg={'d': {'output.entities': '_entities', 'output.cursor': '_cursor', 'output.more': '_more'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'suspend'), arguments={ 'key': orm.SuperKeyProperty(kind='6', required=True), 'message': orm.SuperTextProperty(required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'s': {'_domain.state': 'suspended'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message'}}), CallbackNotify(), CallbackExec() ] ), orm.PluginGroup( plugins=[ RulePrepare(), Set(cfg={'d': {'output.entity': '_domain'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'activate'), arguments={ 'key': orm.SuperKeyProperty(kind='6', required=True), 'message': orm.SuperTextProperty(required=True) }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'s': {'_domain.state': 'active'}}), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message'}}), CallbackNotify(), CallbackExec() ] ), orm.PluginGroup( plugins=[ RulePrepare(), Set(cfg={'d': {'output.entity': '_domain'}}) ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'sudo'), arguments={ 'key': orm.SuperKeyProperty(kind='6', required=True), 'state': orm.SuperStringProperty(required=True, choices=['active', 'suspended', 'su_suspended']), 'message': orm.SuperTextProperty(required=True), 'note': orm.SuperTextProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), Set(cfg={'d': {'_domain.state': 'input.state'}}), RulePrepare(cfg={'skip_user_roles': True}), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message', 'note': 'input.note'}}), RulePrepare(cfg={'skip_user_roles': True}), Set(cfg={'d': {'output.entity': '_domain'}}), CallbackNotify(), CallbackExec() ] ) ] ), orm.Action( key=orm.Action.build_key('6', 'log_message'), arguments={ 'key': orm.SuperKeyProperty(kind='6', required=True), 'message': orm.SuperTextProperty(required=True), 'note': orm.SuperTextProperty() }, _plugin_groups=[ orm.PluginGroup( plugins=[ Context(), Read(), RulePrepare(), RuleExec() ] ), orm.PluginGroup( transactional=True, plugins=[ Write(cfg={'dra': {'message': 'input.message', 'note': 'input.note'}}), Set(cfg={'d': {'output.entity': '_domain'}}), CallbackNotify(), CallbackExec() ] ) ] ) ] @property def key_namespace(self): return self.key.urlsafe() @property def namespace_entity(self): return self