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 Garage(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() address = appier.field( type = appier.reference( "Address", name = "identifier" ), eager = True )
class EWishlistLine(base.EBase): quantity = appier.field(type=float) total = appier.field(type=float) product = appier.field(type=appier.reference("EProduct", name="id"))
class Car(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() brand = appier.field() variant = appier.field() garage = appier.field( type = appier.reference( "Garage", name = "identifier" ), eager = True )
class EnterAction(action.Action): entity = appier.field( type = appier.reference( entity.Entity, name = "identifier" ), observations = """The entity that has just entered a certain area of coverage""" ) @classmethod def list_names(cls): return ["timestamp", "entity", "app"] @classmethod def latest(cls, identifier): return cls.get( entity = identifier, sort = [("timestamp", -1), ("id", -1)], raise_e = False ) @classmethod def last(cls, identifier, limit = 2): return cls.find( entity = identifier, sort = [("timestamp", -1), ("id", -1)], limit = limit ) @classmethod def enter_s( cls, identifier, timestamp = None, info = None, key = None, verify = False ): info = info or dict() if verify: entity.Entity.verify_g(identifier, key) enter = cls( entity = identifier, timestamp = timestamp, info = info ) enter.save() return enter
class Cat(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() friend = appier.field(type=appier.reference("Cat", name="identifier"))
class EOrderLine(base.EBase): quantity = appier.field(type=float) total = appier.field(type=float) size = appier.field(type=int) size_s = appier.field() scale = appier.field(type=int) meta = appier.field(type=dict) meta_j = appier.field() product = appier.field(type=appier.reference("EProduct", name="id")) @classmethod def _build(cls, model, map): super(EOrderLine, cls)._build(model, map) meta = model.get("meta", {}) or {} image_url = meta.get("image_url", None) if not image_url: return product = model["product"] for size in ("thumbnail", "large"): size_i = size + "_image" image = product[size_i] or {} image["url"] = image_url product[size_i] = image @property def size_v(self): return self.size_s if self.size_s else str(self.size)
class Base(appier_extras.admin.Base): id = dict(type=int, index=True, increment=True) unique_id = dict(index=True) enabled = dict(type=bool, index=True) create_date = dict(type=float, index=True) modify_date = dict(type=float, index=True) create_user = dict(type=appier.reference("Account", name="id"), index=True) modify_user = dict(type=appier.reference("Account", name="id"), index=True) description = dict(default=True) @classmethod def _build(cls, model, map): appier_extras.admin.Base._build(model, map) enabled = model.get("enabled", None) model["status"] = enabled and "enabled" or "disabled" @classmethod def lock_g(cls, name): model_name = cls.__name__ model_name_l = model_name.lower() lock_name = "%s_%s" % (model_name_l, name) lock = LOCKS_MAP.get(lock_name, threading.RLock()) lock.acquire() LOCKS_MAP[lock_name] = lock @classmethod def unlock_g(cls, name): model_name = cls.__name__ model_name_l = model_name.lower() lock_name = "%s_%s" % (model_name_l, name) lock = LOCKS_MAP[lock_name] lock.release() @classmethod def _slugify(cls, text, delim=u"-"): result = [] if not type(text) == unicode: text = text.decode("utf-8") _punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') for word in _punct_re.split(text.lower()): result.extend(unidecode.unidecode(word).split()) return unicode(delim.join(result)) @classmethod def _get_timestamp(cls, date=None): date = date or datetime.datetime.utcnow() date_utc = date.utctimetuple() timestamp = calendar.timegm(date_utc) return timestamp def pre_create(self): import account self.unique_id = str(uuid.uuid4()) self.enabled = True self.create_date = self.modify_date = time.time() self.create_user = self.modify_user = account.Account.get_from_session( raise_e=False) def pre_update(self): import account self.modify_date = time.time() self.modify_user = account.Account.get_from_session(raise_e=False) def enable_s(self): if self.enabled: return self.enabled = True self.save() self.trigger("enabled") def disable_s(self): if not self.enabled: return self.enabled = False self.save() self.trigger("disabled") def increment_s(self, name, default=0): # acquires a lock for the attribute self.lock(name) # increments the value value = getattr(self, name) if hasattr(self, name) else default value += 1 setattr(self, name, value) # saves the change and releases the lock collection = self._collection() try: collection.update({"id": self.id}, {"$set": {name: value}}) finally: self.unlock(name) def decrement_s(self, name, default=0): # acquires a lock for the attribute self.lock(name) # decrements the value value = getattr(self, name) if hasattr(self, name) else default value -= 1 setattr(self, name, value) # saves the change and releases the lock collection = self._collection() try: collection.update({"id": self.id}, {"$set": {name: value}}) finally: self.unlock(name) def get_previous(self): import account account_s = account.Account.get_from_session(raise_e=False) _class = self.__class__ entities = _class.find( id={"$lt": self.id}, enabled=True, sort=[ ("id", -1) ], limit=1) if account_s else _class.find( id={"$lt": self.id}, sort=[("id", -1)], limit=1) if not entities: return entity = entities[0] return entity def get_next(self): import account account_s = account.Account.get_from_session(raise_e=False) _class = self.__class__ entities = _class.find( id={"$gt": self.id}, enabled=True, sort=[ ("id", 1) ], limit=1) if account_s else _class.find( id={"$gt": self.id}, sort=[("id", 1)], limit=1) if not entities: return entity = entities[0] return entity def get_previous_id(self): previous = self.get_previous() if not previous: return return previous.id def get_next_id(self): next = self.get_next() if not next: return return next.id def lock(self, name=None): _name = "%d_%s" % (self.id, name or "") self.lock_g(_name) def unlock(self, name=None): _name = "%d_%s" % (self.id, name or "") self.unlock_g(_name) def reload(self): cls = self.__class__ return cls.get(id=self.id) def equals(self, entity): self_id = self.id if hasattr(self, "id") else None entity_id = entity.id if hasattr(entity, "id") else None return self_id == entity_id def is_persisted(self): return hasattr(self, "id") and not self.id == None def is_modified(self): return not self.create_date == self.modify_date def has_previous(self): previous = self.get_previous() return bool(previous) def has_next(self): next = self.get_next() return bool(next) def has(self, name): if not hasattr(self, name): return False if not self.name: return False return True
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 EBagLine(base.EBase): quantity = appier.field( type = float ) total = appier.field( type = float ) size = appier.field( type = int ) size_s = appier.field() scale = appier.field( type = int ) meta = appier.field( type = dict ) meta_j = appier.field() product = appier.field( type = appier.reference( "EProduct", name = "id" ) ) @classmethod def _build(cls, model, map): super(EBagLine, cls)._build(model, map) meta = model.get("meta", {}) or {} image_url = meta.get("image_url", None) embossing = meta.get("embossing", None) if image_url: product = model["product"] for size in ("thumbnail", "large"): size_i = size + "_image" image = product[size_i] or {} image["url"] = image_url product[size_i] = image if embossing: embossing_s = embossing.replace("_", " ") embossing_s = embossing_s.capitalize() meta["embossing_s"] = embossing_s def get_meta(self, normalize = True): if not self.meta: return self.meta meta = dict(self.meta) if not normalize: return meta for extra in ("embossing_s",): if not extra in meta: continue del meta[extra] return meta @property def size_v(self): return self.size_s if self.size_s else str(self.size)
class OAuthToken(base.Base, authenticable.Authenticable): """ Model class that represent an OAuth 2.0 access token that as been in created for a specific user context and with a certain duration scope. """ DEFAULT_DURATION = 3600 """ The default duration of the OAuth token, this value should not be too long to avoid security issues """ DEFAULT_LONG_DURATION = 315360000 """ The default duration (in seconds) value to be used in a long lived OAUth token (eg: 10 years) """ CODE_DURATION = 600 """ The authorization code duration (in seconds), to be used for proper authorization code validation """ name = appier.field( index = "hashed", default = True, safe = True, immutable = True ) """ Simplified name value, created from the first few characters of the (original) access token value """ access_token = appier.field( index = "hashed", safe = True ) """ The actual string representing an authorization issued to the client, this is not an immutable value as the refresh token may be used to generate a new one """ access_token_date = appier.field( type = float, safe = True, private = True, meta = "datetime" ) """ The date when the access token was last generated, multiple access token may be generated using the refresh token""" authorization_code = appier.field( index = "hashed", safe = True, private = True ) """ The authorization code generated by the authorization server to be sent to the client side for future obtain of access token """ authorization_code_date = appier.field( type = float, safe = True, private = True, meta = "datetime" ) """ The date when the authorization code was generated """ username = appier.field( index = "hashed", safe = True, immutable = True ) """ The name of the user that is authorized this access token, will be used for custom ACL creation """ scope = appier.field( type = list, private = True, immutable = True ) """ The OAuth based scope string that "created" this token with its values sorted alphabetically """ expires_in = appier.field( type = int, index = "all", safe = True, immutable = True ) """ The duration in seconds of the access token lifetime """ redirect_uri = appier.field( index = "hashed", immutable = True, meta = "url", description = "Redirect URI" ) """ An absolute URI to which the authorization server will redirect the user-agent to when the end-user authorization step is completed """ refresh_token = appier.field( index = "hashed", safe = True, private = True, immutable = True ) """ A token used by the client to obtain a new access token (in addition or as a replacement for an expired access token), without having to involve the resource owner. """ tokens = appier.field( type = list, safe = True, private = True, immutable = True ) """ The ACL tokens associated with this access token, should be created taking into account the token scope """ client = appier.field( type = appier.reference( "OAuthClient", name = "id" ), immutable = True ) """ The reference to the OAuth client that has been used for the generation of this token """ @classmethod def validate(cls): return super(OAuthToken, cls).validate() + [ appier.not_null("username"), appier.not_empty("username"), appier.not_null("scope"), appier.not_empty("scope"), appier.string_gt("scope", 0), appier.not_null("redirect_uri"), appier.not_empty("redirect_uri"), appier.is_url("redirect_uri"), appier.not_null("client") ] @classmethod def list_names(cls): return ["name", "created", "username"] @classmethod def order_name(cls): return ["id", -1] @classmethod def reuse_s(cls, redirect_uri, scope, oauth_client, account = None, owner = None): # defaults the provided owner value to the global registered # app to be used if required for account defaulting owner = owner or appier.get_app() # retrieves the current account from session and then # normalizes the provided scope list to convert it to # tokens (filters on account permissions) then tries to # retrieve an already existing compatible OAuth token account = account or owner.admin_part.account_c.from_session() tokens = cls._filter_scope_g(scope, account = account) oauth_token = cls.get_e( redirect_uri = redirect_uri, username = account.username, scope = scope, tokens = tokens, client = oauth_client.id, rules = False, raise_e = False ) # in case there's no valid equivalent token, returns the # control flow immediately with an invalid value if not oauth_token: return False, tokens, None # in case the access token contained in the object is already # expired then refreshes the access token so that it can live # one more time for this request (refreshed re-usage scenario) if oauth_token.is_expired(): oauth_token.refresh_access_token_s() # in case there's an already existing OAuth token that # has the same requirements (scope, client, redirect URL) # of the one being requested, then a new authorization code # is generated and the user agent is redirected immediately # as there's no extra need for user interaction oauth_token.set_code_s() # returns a valid result indicating both the retrieved tokens # and the OAuth token that can be used for re-usage return True, tokens, oauth_token @classmethod def login(cls, access_token, rules = False): oauth_token = cls.get_e( access_token = access_token, rules = rules, raise_e = False ) if not oauth_token: raise appier.SecurityError( message = "OAuth token not found", code = 403 ) oauth_token.touch_expired() return oauth_token @classmethod @appier.view( name = "Explorer", parameters = ( ("Access Token", "access_token", str), ("Refresh Token", "refresh_token", str) ) ) def explorer_v(cls, access_token, refresh_token, *args, **kwargs): kwargs["sort"] = kwargs.get("sort", [("id", -1)]) if access_token: kwargs.update(access_token = access_token) if refresh_token: kwargs.update(refresh_token = refresh_token) return appier.lazy_dict( model = cls, kwargs = kwargs, entities = appier.lazy(lambda: cls.find(*args, **kwargs)), page = appier.lazy(lambda: cls.paginate(*args, **kwargs)) ) @classmethod def _filter_scope_g(cls, scope, account = None, owner = None): """ Filters the provided sequence of tokens for the scope, so that only the ones allowed for the requested account are used. This avoid security issues like someone requesting values for a token that is for which the user is not allowed. :type scope: List :param scope: The list of tokens to be filtered. :type account: Account :param account: The account that is going to be used for the filtering of the values, in case none is provided the current account in session is used. :rtype: List :return: The resulting filtering list containing only the tokens for which the provided account is capable. """ # defaults the provided owner value to the global registered # app to be used if required for account defaulting owner = owner or appier.get_app() # builds the list that is going to be used to store the # result of the scope filtering (ACL verification) result = [] # retrieves the complete set of tokens from the account # and then converts them into the map version of them account = account or owner.admin_part.account_c.from_session() tokens = account.tokens() tokens_m = appier.to_tokens_m(tokens) # iterates over each token of the scope to validate it # according to the ACL of the associated account for token in scope: valid = appier.check_token(None, token, tokens_m = tokens_m) if not valid: continue result.append(token) # returns the final result that contains only the scope # tokens for which the account is entitle to register return result @classmethod def _underscore(cls, plural = True): return "oauth_tokens" if plural else "oauth_token" @classmethod def _readable(cls, plural = False): return "OAuth Tokens" if plural else "OAuth Token" def pre_create(self): base.Base.pre_create(self) cls = self.__class__ duration = appier.conf("OAUTH_DURATION", cls.DEFAULT_DURATION, cast = int) long_duration = appier.conf("OAUTH_LONG_DURATION", cls.DEFAULT_LONG_DURATION, cast = int) if hasattr(self, "long") and self.long: duration = long_duration self.access_token = appier.gen_token() self.access_token_date = time.time() self.client_secret = appier.gen_token() self.authorization_code = appier.gen_token() self.authorization_code_date = time.time() self.expires_in = duration self.refresh_token = appier.gen_token() self.tokens = self._filter_scope(self.scope) self.name = self.access_token[:8] self._verify() def set_code_s(self): self.authorization_code = appier.gen_token() self.authorization_code_date = time.time() self.save() def unset_code_s(self): self.authorization_code = None self.authorization_code_date = None self.save() def set_access_token_s(self): self.access_token = appier.gen_token() self.access_token_date = time.time() self.save() def unset_access_token_s(self): self.access_token = None self.access_token_date = None self.save() def refresh_access_token_s(self): self.set_access_token_s() def get_account(self): return self.owner.admin_part.account_c.get( username = self.username ) def is_expired(self): access_token_date = self.access_token_date or self.created return time.time() > access_token_date + self.expires_in def touch_expired(self, delete = None): """ Method to be called upon the token usage so that the expiration for the OAuth token may be checked. If the verification fails it's possible to have the current token removed from the data source if there's no refresh token defined. :type delete: bool :param delete: If the token should be automatically removed from the data source if it's expired (any of the verification fails). """ try: self.verify_expired() except Exception: if delete == None: delete = False if self.refresh_token else True if delete: self.delete() raise def verify_code(self, code, grant_type = "authorization_code"): cls = self.__class__ appier.verify(not self.authorization_code == None) appier.verify(not self.authorization_code_date == None) appier.verify(self.authorization_code == code) appier.verify(time.time() - self.authorization_code_date < cls.CODE_DURATION) appier.verify(grant_type, "authorization_code") def verify_refresh(self, refresh_token, grant_type = "refresh_token"): appier.verify(not self.refresh_token == None) appier.verify(not self.refresh_token == None) appier.verify(self.refresh_token == refresh_token) appier.verify(grant_type, "refresh_token") def verify_expired(self): appier.verify( not self.is_expired(), message = "OAuth access token is expired", code = 403, exception = appier.OperationalError ) def _verify(self): self._verify_scope() def _verify_scope(self): scope_s = set(self.scope) appier.verify(len(self.scope) == len(scope_s)) def _filter_scope(self, scope): """ Filters the provided sequence of tokens for the scope, so that only the ones allowed for the requested user are used. This avoid security issues like someone requesting values for a token that is for which the user is not allowed. :type scope: List :param scope: The list of tokens to be filtered. :rtype: List :return: The resulting filtering list containing only the tokens for which the impersonated user is capable. """ cls = self.__class__ account = self.get_account() return cls._filter_scope_g(scope, account = account) def _set_session(self, unset = True, safes = [], method = "set"): cls = self.__class__ account = self.get_account() account._set_session(unset = unset, safes = safes, method = method) if unset: return set("tokens", self.tokens)
class Label(base.ToolisBase): name = appier.field( default = True, index = "hashed", meta = "text", observations = """Name of the label to be created, this should be as descriptive and simple as possible""" ) attributes = appier.field( meta = "text", observations = """The multiple attributes that describe the label should describe a set of key to value associations""" ) code = appier.field( index = "hashed", meta = "text", observations = """Internal code to be used in the barcode generation process""" ) image = appier.field( type = appier.image( width = 500, height = 500, format = "jpeg" ), private = True, observations = """The image that is going to be used to visually describe the item associated with the label""" ) image_url = appier.field( index = "hashed", meta = "image_url", description = "Image URL", observations = """The URL of the image that is currently associated, it should be a valid absolute value""" ) category = appier.field( type = appier.reference( "Category", name = "name" ), observations = """The logical category to which this label belongs""" ) @classmethod def validate(cls): return super(Label, cls).validate() + [ appier.not_null("name"), appier.not_empty("name"), appier.not_duplicate("code", cls._name()) ] @classmethod def list_names(cls): return ["name", "description", "code", "category"] @classmethod def order_name(cls): return ("id", -1) @classmethod @appier.operation( name = "Create", parameters = ( ("Name", "name", str), ("Description", "description", str), ("Attributes", "attributes", "longtext"), ("Image", "file", "file"), ) ) def create_s(cls, name, description, attributes, image): label = cls( name = name, description = description, attributes = attributes, image = cls.image.type(image) ) label.save() return label @classmethod @appier.link(name = "Left 30x12", context = True) def list_30x12_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_30x12", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Left 98x40", context = True) def list_98x40_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_98x40", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Right 40x25", context = True) def list_40x25_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_40x25", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Right 70x25", context = True) def list_70x25_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_70x25", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Right 55x45", context = True) def list_55x45_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_55x45", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Right 90x57", context = True) def list_90x57_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_90x57", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Right 100x50", context = True) def list_100x50_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_100x50", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Right 145x85", context = True) def list_145x85_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_145x85", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Vertical 25x40", context = True) def list_25x40_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_25x40", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Vertical 25x70", context = True) def list_25x70_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_25x70", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Vertical 50x100", context = True) def list_50x100_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_50x100", view = view, context = context, absolute = absolute ) @classmethod @appier.link(name = "Vertical 85x145", context = True) def list_85x145_url(cls, view = None, context = None, absolute = False): return appier.get_app().url_for( "label.list_85x145", view = view, context = context, absolute = absolute ) def post_create(self): base.ToolisBase.post_create(self) self.set_code_s() self.set_image_url_s() def post_update(self): base.ToolisBase.post_update(self) self.set_image_url_s() @appier.operation(name = "Set Code") def set_code_s(self, force = False): if self.code and not force: return prefix = appier.conf("TOOLIS_LABEL_CODE", "%09d") self.code = prefix % self.id self.save() @appier.operation(name = "Set Image URL") def set_image_url_s(self, force = False): if self.image_url and not force: return self.image_url = self.view_image_url(absolute = True) self.save() @appier.operation( name = "Set Name", parameters = (("Name", "name", appier.legacy.UNICODE),) ) def set_name_s(self, name): if not name: return self.name = name self.save() @appier.operation( name = "Set Category", parameters = ( ( "Category", "category", appier.reference("Category", name = "name") ), ) ) def set_category_s(self, category): if not category: return self.category = category self.save() @appier.operation(name = "Duplicate", factory = True) def duplicate_s(self): cls = self.__class__ instance = self.reload(rules = False) label = cls( name = instance.name, attributes = instance.attributes, image = instance.image, category = instance.category ) label.save() return label @appier.link(name = "View Image", devel = True) def view_image_url(self, absolute = False): cls = self.__class__ return self.owner.url_for( "label.image", id = self.id, absolute = absolute )
class EAccount(base.EBase): username = appier.field() password = appier.field() email = appier.field() first_name = appier.field() last_name = appier.field() gender = appier.field() birth_date = appier.field(type=int) country = appier.field() phone_number = appier.field() receive_newsletters = appier.field(type=bool, initial=False) avatar = appier.field(type=appier.File) facebook_id = appier.field() facebook_token = appier.field(private=True) google_id = appier.field() google_token = appier.field(private=True) bag = appier.field(type=appier.reference("EBag", name="id")) wishlist = appier.field(type=appier.reference("EWishlist", name="id")) def tokens(self): return ["user"] @classmethod def validate(cls): return super(EAccount, cls).validate() + [ appier.not_null("username"), appier.not_empty("username"), appier.string_gt("username", 3), appier.not_duplicate("username", cls._name()), appier.not_null("email"), appier.not_empty("email"), appier.is_email("email"), appier.not_duplicate("email", cls._name()), appier.not_null("first_name"), appier.not_empty("first_name"), appier.is_regex("phone_number", "^\+?[0-9\s]{2,}$"), appier.equals("password_confirm", "password"), appier.equals("new_password_confirm", "new_password") ] @classmethod def validate_new(cls): return super(EAccount, cls).validate_new() + [ appier.not_null("password"), appier.not_empty("password"), appier.not_null("password_confirm"), appier.not_empty("password_confirm") ] @property def full_name(self): name = self.first_name name += " " + self.last_name if self.last_name else "" return name @property def birth_date_s(self): if not self.birth_date: return None return self.string_from_timestamp(self.birth_date)