class Person(appier.Model): identifier = appier.field(type=int, index=True, increment=True, default=True) identifier_safe = appier.field(type=int, index=True, increment=True, safe=True) name = appier.field() age = appier.field(type=int) info = appier.field(type=dict) father = appier.field( type=appier.reference("Person", name="identifier", dumpall=True)) brother = appier.field(type=appier.reference("Person", name="identifier")) car = appier.field(type=appier.reference("Car", name="identifier"), eager=True) cats = appier.field(type=appier.references("Cat", name="identifier")) @classmethod def validate(cls): return super(Person, cls).validate() + [ appier.not_null("name"), appier.not_empty("name"), appier.not_duplicate("name", cls._name()) ]
class EWishlist(base.EBase): currency = appier.field() total = appier.field( type = float ) lines = appier.field( type = appier.references( "EWishlistLine", name = "id" ) )
class EBag(base.EBase): currency = appier.field() total = appier.field( type = float ) lines = appier.field( type = appier.references( "EBagLine", name = "id" ) ) @property def quantity(self): return sum([line.quantity for line in self.lines])
class Role(base.Base): name = appier.field( index = "all", default = True ) """ The common name to be used to identify this role, this is going to be the primary way of identifying it """ tokens = appier.field( type = list ) """ The set of ACL token that are going to be used to control the permission of accounts that use this role """ view = appier.field( type = dict ) """ The filtered view that is going to be applied for every filtered operation (data source access) """ children = appier.field( type = appier.references( "Role", name = "id" ) ) """ The complete set of child roles that are associated with this role, this role should inherit all og the characteristics of the child roles (expected behaviour) """ @classmethod def setup(cls): super(Role, cls).setup() # tries to find the owner role (default) in case it's not # found returns immediately nothing to be done owner = cls.find(name = "owner") if owner: return # retrieves the reference to the global logger that is going # to be used (should be initialized) and then prints the initial # information about the role to be generated logger = appier.get_logger() logger.info("Generating initial owner role for %s model ..." % cls.__name__) # creates the structure to be used as the owner role description # using the default value and then stores the role role = cls( name = "owner", description = "Super administrator role", tokens = ["*"] ) role.save(validate = False) @classmethod def validate(cls): return super(Role, cls).validate() + [ appier.not_null("name"), appier.not_empty("name"), appier.is_lower("name"), appier.string_gt("name", 3), appier.string_lt("name", 64), appier.not_duplicate("name", cls._name()), appier.not_null("tokens"), appier.not_empty("tokens") ] @classmethod def list_names(cls): return ["name", "description"] def view_m(self, context = None): return self.view @property def tokens_a(self): tokens = set(self.tokens) for child in self.children: tokens.update(child.tokens_a) return tokens @property def meta_a(self): meta = dict(self.meta) for child in self.children: for key, value in appier.legacy.iteritems(child.meta): if key in meta: previous = meta[key] if not type(previous) == list: previous = [previous] if not value in previous: value = previous + [value] else: value = previous meta[key] = value return meta @appier.operation( name = "Duplicate", description = """Create a new account with exactly the same specification as the current one""", parameters = (("Suffix", "suffix", str, "-new"),), factory = True ) def duplicate_s(self, suffix = "-new"): cls = self.__class__ role = cls( description = self.description, meta = self.meta, name = self.name + suffix, tokens = self.tokens, view = self.view ) role.save() return role @appier.operation( name = "Set Parent", parameters = (("Name", "name", str),) ) def set_parent_s(self, name): cls = self.__class__ parent = cls.get(name = name) if self in parent.children: return parent.children.append(self) parent.save() @appier.operation( name = "Add Child", parameters = (("Name", "name", str),) ) def add_child_s(self, name): cls = self.__class__ child = cls.get(name = name) if child in self.children: return self.children.append(child) self.save()
class Account(base.Base, authenticable.Authenticable): ADMIN_TYPE = 1 USER_TYPE = 2 ROLE_TYPE = 3 ACCOUNT_S = {ADMIN_TYPE: "admin", USER_TYPE: "user", ROLE_TYPE: "role"} PREFIXES = ["fb.", "tw.", "gg.", "gh.", "live.", "params."] ROOT_USERNAME = "******" ROOT_PASSWORD = "******" ROOT_EMAIL = "*****@*****.**" username = appier.field(index="all", default=True) email = appier.field(index="all", immutable=True, meta="email") password = appier.field(private=True, meta="secret") key = appier.field(safe=True, private=True, meta="secret") reset_token = appier.field(safe=True, private=True, meta="secret") confirmation_token = appier.field(safe=True, private=True, meta="secret") facebook_id = appier.field(index="hashed", safe=True, description="Facebook ID") github_login = appier.field(index="hashed", safe=True, description="GitHub Login") google_id = appier.field(index="hashed", safe=True, description="Google ID") live_id = appier.field(index="hashed", safe=True, description="Live ID") twitter_username = appier.field(index="hashed", safe=True) facebook_token = appier.field(private=True, meta="secret") github_token = appier.field(private=True, meta="secret") google_token = appier.field(private=True, meta="secret") live_token = appier.field(private=True, meta="secret") twitter_token = appier.field(private=True, meta="secret") type = appier.field(type=int, initial=USER_TYPE, safe=True, meta="enum", enum=ACCOUNT_S) last_login = appier.field(type=int, safe=True, meta="datetime") avatar = appier.field(type=appier.image(width=400, height=400, format="png"), private=True) roles = appier.field(type=appier.references("Role", name="id")) @classmethod def setup(cls): super(Account, cls).setup() # tries to find the root account (default) in case it's not # found returns immediately nothing to be done root = cls.find(username="******") if root: return # retrieves the reference to the global logger that is going # to be used (should be initialized) and then prints the initial # information about the account to be generated logger = appier.get_logger() logger.info("Generating initial root account for %s model ..." % cls.__name__) # creates the structure to be used as the root account description # using the default value and then stores the account as it's going # to be used as the default root entity (for administration) account = cls(enabled=True, username=cls.ROOT_USERNAME, email=cls.ROOT_EMAIL, password=cls.generate(cls.ROOT_PASSWORD), type=cls.ADMIN_TYPE) account.save(validate=False) # tries to retrieve the newly generated account with no rules and then # uses it to print information about the newly created account account = cls.get(id=account.id, rules=False) logger.info("Username: %s" % account.username) logger.info("Password: %s" % cls.ROOT_PASSWORD) logger.info("Secret Key: %s" % account.key) @classmethod def validate(cls): return super(Account, cls).validate() + [ appier.not_null("username"), appier.not_empty("username"), appier.is_lower("username"), appier.string_gt("username", 3), appier.string_lt("username", 64), appier.not_duplicate("username", cls._name()), appier.not_null("email"), appier.not_empty("email"), appier.is_lower("email"), appier.is_email("email"), appier.not_duplicate("email", cls._name()), appier.not_empty("password"), appier.string_gt("password", 3), appier.string_lt("password", 256), appier.not_null("type"), appier.not_empty("password_confirm"), appier.string_gt("password_confirm", 3), appier.string_lt("password_confirm", 256), appier.equals("password_confirm", "password") ] @classmethod def validate_new(cls): return super(Account, cls).validate_new() + [ appier.not_null("password"), appier.not_null("password_confirm") ] @classmethod def list_names(cls): return ["username", "email", "last_login", "type", "enabled"] @classmethod def extra_names(cls): return super(Account, cls).extra_names() + ["password_confirm"] @classmethod def login(cls, username, password, insensitive=True): # in case the (case) insensitive option is enabled and the username # is defined the value is converted to lower case so that a proper # comparison may be used (not case sensitive) if insensitive and username: username = username.lower() # verifies that both the provided username and password are valid # and that are correctly and properly defined (required for validation) if not username or not password: raise appier.OperationalError( message="Both username and password must be provided", code=400) # tries to retrieve the account with the provided username, so that # the other validation steps may be done as required by login operation account = cls.get(username=username, rules=False, build=False, raise_e=False) if not account: raise appier.OperationalError(message="No valid account found", code=403) # verifies that the retrieved account is currently enabled, because # disabled accounts are not considered to be valid ones if not account.enabled: raise appier.OperationalError(message="Account is not enabled", code=403) # retrieves the value of the password for the stored account and then # verifies that the value matched the one that has been provided _password = account.password if not cls.verify(_password, password): raise appier.OperationalError( message="Invalid or mismatch password", code=403) # "touches" the current account meaning that the last login value will be # updated to reflect the current time and then returns the current logged # in account to the caller method so that it may used (valid account) account.touch_login_s() return account @classmethod def login_key(cls, key): # verifies that secret key is provided, is considered valid for domain # and that it is correctly and properly defined (required for validation) if not key: raise appier.OperationalError( message="Secret key must be provided", code=400) # tries to retrieve the account with the provided username, so that # the other validation steps may be done as required by login operation account = cls.get(key=key, rules=False, build=False, raise_e=False) if not account: raise appier.OperationalError(message="No valid account found", code=403) # verifies that the retrieved account is currently enabled, because # disabled accounts are not considered to be valid ones if not account.enabled: raise appier.OperationalError(message="Account is not enabled", code=403) # "touches" the current account meaning that the last login value will be # updated to reflect the current time and then returns the current logged # in account to the caller method so that it may used (valid account) account.touch_login_s() return account @classmethod def confirm(cls, confirmation_token, send_email=False): # validates the current account and token for confirmation and # if that's valid runs the confirm operation and returns the account # associated to the caller method account = cls.validate_confirmation(confirmation_token) account.confirm_s(send_email=send_email) return account @classmethod def recover(cls, identifier, send_email=False): # verifies if a valid identifier has been provided because that value # is required for the proper account recover process to be executed if not identifier: raise appier.OperationalError( message="An identifier must be provided", code=400) # creates the keyword based arguments that are going to be used to provide # an extra layer of or based filtering to the retrieval process of the account # that is going to be performed next kwargs = {"$or": [dict(username=identifier), dict(email=identifier)]} account = cls.get(build=False, raise_e=False, **kwargs) # in case no account has been retrieved an error is raised indicating such # problem, as that is required for the account recover process if not account: raise appier.OperationalError(message="No valid account found", code=403) # runs the recover instance method that should generate a new reset token # for the account and send the proper notifications return account.recover_s(send_email=send_email) @classmethod def reset(cls, reset_token, password, password_confirm, confirm=True): account = cls.validate_reset(reset_token) if confirm: account.confirm_s() account.reset_s(password, password_confirm) return account @classmethod def verify(cls, encoded, decoded): type, salt, digest, plain = cls.unpack(encoded) if plain: return encoded == decoded if salt: decoded += salt type = type.lower() decoded = appier.legacy.bytes(decoded) hash = hashlib.new(type, decoded) _digest = hash.hexdigest() return _digest == digest @classmethod def generate(cls, password, type="sha256", salt="appier"): if cls.is_encrypted(password): return password if type == "plain": return password if salt: password += salt password = appier.legacy.bytes(password) hash = hashlib.new(type, password) digest = hash.hexdigest() if not salt: return "%s:%s" % (type, digest) salt = appier.legacy.bytes(salt) salt = binascii.hexlify(salt) salt = appier.legacy.str(salt) return "%s:%s:%s" % (type, salt, digest) @classmethod def unpack(cls, password): count = password.count(":") if count == 2: type, salt, digest = password.split(":") elif count == 1: type, digest = password.split(":") salt = None else: plain = password type = "plain" salt = None digest = None if not type == "plain": plain = None if salt: salt = appier.legacy.bytes(salt) if salt: salt = binascii.unhexlify(salt) if salt: salt = appier.legacy.str(salt) return (type, salt, digest, plain) @classmethod def validate_reset(cls, reset_token): account = cls.get(reset_token=reset_token, raise_e=False) if account: return account raise appier.SecurityError(message="Invalid reset token") @classmethod def validate_confirmation(cls, confirmation_token): account = cls.get(confirmation_token=confirmation_token, raise_e=False) if account: return account raise appier.SecurityError(message="Invalid confirmation token") @classmethod def from_session(cls, *args, **kwargs): session = appier.get_session() if not "username" in session: return None return cls.get(username=session["username"], *args, **kwargs) @classmethod def is_encrypted(cls, password): return password.count(":") > 0 @classmethod @appier.operation(name="Create", description="""Create a new account with the provided username, email and password, in case no password is provided a random new one is generated""", parameters=(("Username", "username", str), ("Email", "email", str), ("Password", "password", str, ""), ("Send Email", "send_email", bool, False)), factory=True) def create_s(cls, username, email, password="", send_email=False): if not password: password = appier.gen_token(limit=16) account = cls(username=username, email=email, password=password, password_confirm=password) account.save() if send_email: account.email_new(password=password) return account @classmethod @appier.operation(name="Import JSON", parameters=(("JSON File", "file", "file"), ("Empty source", "empty", bool, False))) def import_json_s(cls, file, empty): def callback(item): enabled = item["enabled"] username = item["username"] type = item["type"] password = item.get("password", None) email = item.get("email", None) key = item.get("key", None) description = item.get("description", None) last_login = item.get("last_login", None) avatar = item.get("avatar", None) roles = item.get("roles", []) account = cls(enabled=enabled, username=username, type=type, password=password, password_confirm=password, email=email, key=key, description=description, last_login=last_login, avatar=avatar, roles=roles) account.save() if empty: cls.delete_c() cls._json_import(file, callback) @classmethod @appier.operation(name="Import CSV", parameters=(("CSV File", "file", "file"), ("Empty source", "empty", bool, False))) def import_csv_s(cls, file, empty=False): def callback(line): username,\ password,\ email,\ type = line if type and type == "admin": type = cls.ADMIN_TYPE else: type = cls.USER_TYPE account = cls(enabled=True, username=username, email=email, password=password, password_confirm=password, type=type) account.save() if empty: cls.delete_c() cls._csv_import(file, callback) @classmethod def _build(cls, model, map): super(Account, cls)._build(model, map) username = model["username"] model["avatar_url"] = cls._get_avatar_url_g(username) @classmethod def _unset_session(cls, prefixes=None, safes=[], method="delete"): prefixes = prefixes or cls.PREFIXES session = appier.get_session() delete = getattr(session, method) if "cls" in session: delete("cls") if "username" in session: delete("username") if "name" in session: delete("name") if "email" in session: delete("email") if "type" in session: delete("type") if "tokens" in session: delete("tokens") if "views" in session: delete("views") if "meta" in session: delete("meta") if "params" in session: delete("params") for key in appier.legacy.keys(session): is_removable = False for prefix in prefixes: is_safe = key in safes if is_safe: continue is_prefix = key.startswith(prefix) if not is_prefix: continue is_removable = True break if not is_removable: continue delete(key) @classmethod def _get_avatar_url_g(cls, username, absolute=True, owner=None): owner = owner or appier.get_app() if not hasattr(owner, "admin_part"): return None model = None if cls._is_master() else cls._name() return owner.url_for("admin.avatar_account", username=username, cls=model, absolute=absolute) @classmethod def _is_master(cls, owner=None): owner = owner or appier.get_app() admin_part = owner.admin_part return cls == admin_part.account_c def pre_validate(self): base.Base.pre_validate(self) if hasattr(self, "username") and self.username: self.username = self.username.lower() if hasattr(self, "email") and self.email: self.email = self.email.lower() if hasattr(self, "roles") and len(self.roles_l) > 0: self.type = Account.ROLE_TYPE def pre_save(self): base.Base.pre_save(self) if hasattr(self, "password") and self.password: self.password = self.encrypt(self.password) def pre_create(self): base.Base.pre_create(self) if not hasattr(self, "key") or not self.key: self.key = self.secret() if not hasattr(self, "confirmation_token") or\ not self.confirmation_token: self.confirmation_token = self.secret() if not hasattr(self, "avatar") or not self.avatar: self._set_avatar_d() def confirm_s(self, send_email=False): self.confirmation_token = None self.enabled = True if send_email: self.email_confirm() self.save() def recover_s(self, send_email=False): self.reset_token = self.secret() self.save() if send_email: self.email_recover() return self.reset_token def reset_s(self, password, password_confirm): if not password: raise appier.OperationalError(message="No password provided", code=400) if not password == password_confirm: raise appier.OperationalError( message="Invalid password confirmation", code=400) self.password = password self.password_confirm = password_confirm self.reset_token = None self.save() def tokens(self): """ Retrieves the complete set of ACL tokens for the current use respecting the wild card based selection if there's such use. These should consider both dynamic and static role users. This method has a critical impact on overall system security. :rtype: List :return: The ACL tokens list taking into consideration the complete aggregated set of roles of the user. """ tokens = set() if self.type == Account.ADMIN_TYPE: tokens.update(["*"]) if self.type == Account.USER_TYPE: tokens.update(["base", "user"]) for role in self.roles_l: tokens.update(role.tokens_a) if "*" in tokens: tokens = ["*"] tokens = list(tokens) tokens.sort() return tokens def views_l(self): """ Retrieves the complete set of views for the current account, these should take into consideration the account's roles. Aggregation of the multiple views should be done in a linear fashion with the first being the most relevant one. :rtype: List :return: The list of view maps that should be applied for proper context filtering of the user (result set constrain). """ views = [] for role in self.roles_l: view_m = role.view_m(context=self) if not view_m: continue views.append(view_m) return views def meta_m(self, join=False): """ Merges the metadata dictionary of the current account to the ones defined in the complete set of associated roles. :type join: bool :param join: If the overlapping values between the operations should be merged as sequences. :rtype: Dictionary :return: The final meta-data dictionary that contains the "merged" view of the metadata for the account according to the associated set of roles. """ meta = dict(self.meta) for role in self.roles_l: if join: for key, value in appier.legacy.iteritems(role.meta_a): if key in meta: previous = meta[key] if not type(previous) == list: previous = [previous] if not value in previous: value = previous + [value] else: value = previous meta[key] = value else: meta.update(role.meta_a) return meta def type_s(self, capitalize=False): type_s = Account.ACCOUNT_S.get(self.type, None) type_s = type_s.capitalize() if capitalize else type_s return type_s def encrypt(self, value): cls = self.__class__ return cls.generate(value) def _send_avatar(self, image="avatar.png", strict=False, cache=False): admin_part = self.owner.admin_part avatar = self.avatar if hasattr(self, "avatar") else None if not avatar: if strict: raise appier.NotFoundError( message="Avatar not found for user '%s'" % self.username) return self.owner.send_static("images/" + image, static_path=admin_part.static_path) return self.owner.send_file(avatar.data, content_type=avatar.mime, etag=avatar.etag, cache=cache) def _set_avatar_d(self, image="avatar.png", mime="image/png"): if not hasattr(self.owner, "admin_part"): return if not self.owner.admin_part: return if not self.owner.admin_avatar_default: return admin_part = self.owner.admin_part file = open(admin_part.static_path + "/images/" + image, "rb") try: data = file.read() finally: file.close() file_t = (image, mime, data) self.avatar = appier.File(file_t) def _get_avatar_url(self, absolute=True, owner=None): cls = self.__class__ return cls._get_avatar_url_g(self.username, absolute=absolute, owner=self.owner) @appier.operation(name="Touch Login") def touch_login_s(self): # retrieves the global reference to the account class so that # it can be used for the triggering of global operations cls = self.__class__ # updates the last login of the account with the current timestamp # and saves the account so that this value is persisted self.last_login = int(time.time()) self.save() # triggers the global event indicating that a new account has been # touched about a new login operation (allows proper notification) cls.trigger_g("touch_login", self) @appier.operation(name="Generate Key", level=2) def generate_key_s(self, force=False): self = self.reload(rules=False) if self.key and not force: return self.key = self.secret() self.save() @appier.operation(name="Mark Unconfirmed", level=2) def mark_unconfirmed_s(self): self.enabled = False self.confirmation_token = self.secret() self.save() @appier.operation(name="Email New", level=2) def email_new(self, password=None): account = self.reload(rules=False, meta=True) base.Base.send_email_g(self.owner, "admin/email/account/new.html.tpl", receivers=[self.email_f], subject="New account", title="New account", account=account, account_password=password) @appier.operation(name="Email Confirm", level=2) def email_confirm(self): account = self.reload(rules=False, meta=True) base.Base.send_email_g(self.owner, "admin/email/account/confirm.html.tpl", receivers=[self.email_f], subject="Confirm account", title="Confirm account", account=account) @appier.operation(name="Email Recover", level=2) def email_recover(self): account = self.reload(rules=False, meta=True) base.Base.send_email_g(self.owner, "admin/email/account/recover.html.tpl", receivers=[self.email_f], subject="Recover account", title="Recover account", account=account) @appier.operation(name="Upload Avatar", parameters=(("Avatar", "avatar", "file"), )) def upload_avatar_s(self, avatar): cls = self.__class__ if not avatar: return self.avatar = cls.avatar.type(avatar) self.save() @appier.operation(name="Add Role", parameters=(("Name", "name", str), )) def add_role_s(self, name): _role = role.Role.get(name=name) if _role in self.roles_l: return self.roles_l.append(_role) self.save() @appier.operation(name="Remove Role", parameters=(("Name", "name", str), )) def remove_role_s(self, name): _role = role.Role.get(name=name) if not _role in self.roles_l: return self.roles_l.remove(_role) self.save() @appier.link(name="View Avatar", devel=True) def view_avatar_url(self, absolute=False): cls = self.__class__ model = None if cls._is_master() else cls._name() return self.owner.url_for("admin.avatar_account", username=self.username, cls=model, absolute=absolute) @classmethod @appier.link(name="Export JSON", context=True) def export_url(cls, view=None, context=None, absolute=False): return appier.get_app().url_for("admin.export_accounts_json", view=view, context=context, absolute=absolute) @property def confirmed(self): return self.enabled @property def email_f(self): if not self.email: return self.email if not self.username: return self.email return "%s <%s>" % (self.username, self.email) @property def roles_l(self): return self.roles def _set_session(self, unset=True, safes=[], method="set"): cls = self.__class__ if unset: cls._unset_account(safes=safes) self.session.ensure() set = getattr(self.session, method) set("cls", cls.__name__) set("username", self.username) set("name", self.email) set("email", self.email) set("type", self.type_s()) set("tokens", self.tokens()) set("views", self.views_l()) set("meta", self.meta) set("params", dict())
class EOrder(base.EBase): status = appier.field() paid = appier.field(type=bool) date = appier.field(type=int) reference = appier.field() currency = appier.field() sub_total = appier.field(type=float) discount = appier.field(type=float) shipping_cost = appier.field(type=float) total = appier.field(type=float) email = appier.field() shipping_address = appier.field( type=appier.reference("EAddress", name="id")) billing_address = appier.field( type=appier.reference("EAddress", name="id")) lines = appier.field(type=appier.references("EOrderLine", name="id")) vouchers = appier.field(type=appier.references("EVoucher", name="id")) @classmethod def _build(cls, model, map): super(EOrder, cls)._build(model, map) date = model["date"] if date: date = datetime.datetime.utcfromtimestamp(date) model["date_s"] = date.strftime("%Y-%m-%d") if date else None lines = model.get("lines", []) model["quantity"] = sum([line["quantity"] for line in lines]) @property def voucher(self): if not hasattr(self, "vouchers"): return if not self.vouchers: return return self.vouchers[0] @property def quantity(self): return sum([line.quantity for line in self.lines]) @property def date_s(self): return self.get_date_s() def get_date_s(self, format="%Y-%m-%d"): if not hasattr(self, "date"): return None if not self.date: return None date = datetime.datetime.utcfromtimestamp(self.date) return date.strftime(format) def is_finalized(self): return True
class Role(base.Base): name = appier.field(index="all", default=True) """ The common name to be used to identify this role, this is going to be the primary way of identifying it """ tokens = appier.field(type=list) """ The set of ACL token that are going to be used to control the permission of accounts that use this role """ view_ = appier.field(type=dict, description="View") """ The filtered view that is going to be applied for every filtered operation (data source access) """ children = appier.field(type=appier.references("Role", name="id")) """ The complete set of child roles that are associated with this role, this role should inherit all of the characteristics of the child roles (expected behaviour) """ @classmethod def setup(cls): super(Role, cls).setup() # tries to find the owner role (default) in case it's not # found returns immediately nothing to be done owner = cls.find(name="owner") if owner: return # retrieves the reference to the global logger that is going # to be used (should be initialized) and then prints the initial # information about the role to be generated logger = appier.get_logger() logger.info("Generating initial owner role for %s model ..." % cls.__name__) # creates the structure to be used as the owner role description # using the default value and then stores the role role = cls(name="owner", description="Super administrator role", tokens=["*"]) role.save(validate=False) @classmethod def validate(cls): return super(Role, cls).validate() + [ appier.not_null("name"), appier.not_empty("name"), appier.is_lower("name"), appier.string_gt("name", 3), appier.string_lt("name", 64), appier.not_duplicate("name", cls._name()), appier.not_null("tokens") ] @classmethod def list_names(cls): return ["name", "description"] def view_m(self, context=None): return self.view_ @property def children_s(self): return [ child for child in self.children if child and hasattr(child, "tokens_a") ] @property def tokens_a(self): tokens = set(self.tokens) for child in self.children_s: tokens.update(child.tokens_a) return tokens @property def meta_a(self): meta = dict(self.meta) for child in self.children_s: self._join_m(child.meta_a, meta) return meta @property def secrets_a(self): secrets = dict(self.secrets) for child in self.children: self._join_m(child.secrets_a, secrets) return secrets def _join_m(self, origin, target): for key, value in appier.legacy.iteritems(origin): if key in target: previous = target[key] if isinstance(previous, dict): value = self._join_m(value, previous) else: if not isinstance(previous, list): previous = [previous] if not value in previous: value = previous + [value] else: value = previous target[key] = value return target @appier.operation(name="Duplicate", description="""Create a new account with exactly the same specification as the current one""", parameters=(("Suffix", "suffix", str, "-new"), ), factory=True) def duplicate_s(self, suffix="-new"): cls = self.__class__ role = cls(description=self.description, meta=self.meta, name=self.name + suffix, tokens=self.tokens, view=self.view_) role.save() return role @appier.operation(name="Set Parent", parameters=(("Name", "name", str), )) def set_parent_s(self, name): cls = self.__class__ parent = cls.get(name=name) if self in parent.children: return parent.children.append(self) parent.save() @appier.operation(name="Add Child", parameters=(("Name", "name", str), )) def add_child_s(self, name): cls = self.__class__ child = cls.get(name=name) if child in self.children: return self.children.append(child) self.save() @appier.operation(name="Remove Child", parameters=(("Name", "name", str), )) def remove_child_s(self, name): cls = self.__class__ child = cls.get(name=name) if not child in self.children: return self.children.remove(child) self.save() @appier.operation(name="Fix Children", level=2) def fix_children_s(self): self.children = [ child for child in self.children if child and hasattr(child, "tokens_a") ] self.save() @appier.view(name="Accounts") def accounts_v(self, *args, **kwargs): from appier_extras.parts.admin.models import account cls = account.Account kwargs["sort"] = kwargs.get("sort", [("id", -1)]) kwargs.update(roles={"$in": [self.id]}) return appier.lazy_dict( model=cls, kwargs=kwargs, entities=appier.lazy(lambda: cls.find(*args, **kwargs)), page=appier.lazy(lambda: cls.paginate(*args, **kwargs)))