def test_to_struct(): s = utils.to_struct(n="Hello", b="Bye") assert s.n == "Hello"
def model(UserModel): """ Post Model :param UserModel: """ db = UserModel.db class SlugNameMixin(object): name = db.Column(db.String(255), index=True) slug = db.Column(db.String(255), index=True, unique=True) description = db.Column(db.String(255)) image_url = db.Column(db.Text) @classmethod def get_by_slug(cls, slug=None, name=None): """ Return a post by slug """ if name and not slug: slug = utils.slugify(name) return cls.all().filter(cls.slug == slug).first() @classmethod def new(cls, name, slug=None): slug = utils.slugify(name if not slug else slug) return cls.create(name=name, slug=slug) def rename(self, name, slug=None): slug = utils.slugify(name if not slug else slug) return self.update(name=name, slug=slug) class PublisherType(SlugNameMixin, db.Model): """ Types """ @property def total_posts(self): return PublisherPost.all().filter(PublisherPost.type_id == self.id).count() class PublisherCategory(SlugNameMixin, db.Model): """ Category """ @property def total_posts(self): return PublisherCategoryMap.all()\ .filter(PublisherCategoryMap.category_id == self.id)\ .count() class PublisherTag(SlugNameMixin, db.Model): """ Tag """ @property def total_posts(self): return PublisherTagMap.all()\ .filter(PublisherTagMap.tag_id == self.id)\ .count() class PublisherTagMap(db.Model): """ PostPostTag """ post_id = db.Column(db.Integer, db.ForeignKey("publisher_post.id", ondelete='CASCADE')) tag_id = db.Column(db.Integer, db.ForeignKey(PublisherTag.id, ondelete='CASCADE')) @classmethod def add(cls, post_id, tag_id): c = cls.all().filter(cls.post_id == post_id)\ .filter(cls.tag_id == tag_id)\ .first() if not c: cls.create(post_id=post_id, tag_id=tag_id) @classmethod def remove(cls, post_id, tag_id): c = cls.all().filter(cls.post_id == post_id)\ .filter(cls.tag_id == tag_id)\ .first() if c: c.delete(hard_delete=True) class PublisherCategoryMap(db.Model): post_id = db.Column(db.Integer, db.ForeignKey("publisher_post.id", ondelete='CASCADE')) category_id = db.Column(db.Integer, db.ForeignKey(PublisherCategory.id, ondelete='CASCADE')) @classmethod def add(cls, post_id, category_id): c = cls.all().filter(cls.post_id == post_id)\ .filter(cls.category_id == category_id)\ .first() if not c: cls.create(post_id=post_id, category_id=category_id) @classmethod def remove(cls, post_id, category_id): c = cls.all().filter(cls.post_id == post_id)\ .filter(cls.category_id == category_id)\ .first() if c: c.delete(hard_delete=True) class PublisherPost(db.Model): user_id = db.Column(db.Integer, db.ForeignKey(UserModel.id)) type_id = db.Column(db.Integer, db.ForeignKey(PublisherType.id)) title = db.Column(db.String(255)) slug = db.Column(db.String(255), index=True) content = db.Column(db.Text) description = db.Column(db.Text) featured_image = db.Column(db.Text) featured_embed = db.Column(db.Text) featured_media_top = db.Column(db.String(10)) language = db.Column(db.String(255)) parent_id = db.Column(db.Integer) # If the post is derived from another post is_child = db.Column(db.Boolean, index=True, default=False) # is_list = db.Column(db.Boolean, index=True, default=False) # A list is a type of post having sub post is_featured = db.Column(db.Boolean, index=True, default=False) # Feature post are limited featured_at = db.Column(db.DateTime) is_sticky = db.Column(db.Boolean, index=True, default=False) # A sticky post usually stay on top, no matter the count sticky_at = db.Column(db.DateTime) is_published = db.Column(db.Boolean, index=True, default=True) published_at = db.Column(db.DateTime) published_by = db.Column(db.Integer) is_revision = db.Column(db.Boolean, default=False) revision_id = db.Column(db.Integer) # When updating the post, will auto-save is_public = db.Column(db.Boolean, index=True, default=False) is_draft = db.Column(db.Boolean, index=True, default=False) options_data = db.Column(db.Text, default="{}") menu_order = db.Column(db.Integer, default=0, index=True) author = db.relationship(UserModel, backref="posts") type = db.relationship(PublisherType, backref="posts") categories = db.relationship(PublisherCategory, secondary=PublisherCategoryMap.__table__.name) tags = db.relationship(PublisherTag, secondary=PublisherTagMap.__table__.name) @classmethod def new(cls, title, **kwargs): """ Insert a new post """ published_date = None is_revision = False is_published = False is_draft = False is_public = kwargs.get("is_public", True) parent_id = kwargs.get("parent_id", None) if kwargs.get("is_revision"): if not parent_id: raise ModelError("'parent_id' is missing for revision") is_revision = True is_public = False elif kwargs.get("is_draft"): is_draft = True is_public = False elif kwargs.get("is_published"): is_published = True published_date = datetime.datetime.now() slug = None if is_published or is_draft: slug = cls.create_slug(kwargs.get("slug", title)) type_id = kwargs.get("type_id") if not type_id and kwargs.get("type_slug"): type_slug = kwargs.get("type_slug") _type = PublisherType.get_by_slug(slug=type_slug) if _type: type_id = _type.id data = { "user_id": kwargs.get("user_id", 0), "title": title, "slug": slug, "content": kwargs.get("content"), "description": kwargs.get("description"), "is_published": is_published, "published_at": published_date, "is_draft": is_draft, "is_revision": is_revision, "is_public": is_public, "parent_id": parent_id, "type_id": type_id } return cls.create(**data) @classmethod def get_published(cls, id=None, slug=None, types=[], categories=[], tags=[]): """ Return published posts. If $id or $slug it will return a single post, else all :param id: int - the id of a post :param slug: string - the slug of a post :param types: list - list of types slugs :param categories: list - list of categories slug :param tags: list - list of tags slugs :return: """ q = cls.all().filter(cls.is_published == True) # Query only a single post if id or slug: if id: q = q.filter(cls.id == id) elif slug: q = q.filter(cls.slug == slug) return q.first() # Query lists else: if types: q = q.join(PublisherType)\ .filter(PublisherType.slug.in_(types)) if categories: q = q.join(PublisherCategoryMap)\ .join(PublisherCategory)\ .filter(PublisherCategory.slug.in_(categories)) if tags: q = q.join(PublisherTag)\ .filter(PublisherTag.slug.in_(tags)) return q @classmethod def create_slug(cls, title): slug = None slug_counter = 0 _slug = utils.slugify(title).lower() while True: slug = _slug if slug_counter > 0: slug += str(slug_counter) slug_counter += 1 if not cls.get_by_slug(slug): break return slug @classmethod def get_by_slug(cls, slug): """ Return a post by slug """ return cls.all().filter(cls.slug == slug).first() def publish(self, published_date=None, published_by_id=None): if self.is_draft: data = { "is_draft": False, "is_published": True, "published_at": published_date or datetime.datetime.now() } if published_by_id: data.update({ "published_by": published_by_id }) self.update(**data) def set_slug(self, title): slug = utils.slugify(title) if title and slug != self.slug: slug = self.create_slug(slug) self.update(slug=slug) def update_categories(self, categories_list): """ Update categories by replacing existing list with new list :param categories_list: list. The new list of category """ cats = PublisherCategoryMap.all()\ .filter(PublisherCategoryMap.post_id == self.id) cats_list = [c.category_id for c in cats] del_cats = list(set(cats_list) - set(categories_list)) new_cats = list(set(categories_list) - set(cats_list)) for dc in del_cats: PublisherCategoryMap.remove(post_id=self.id, category_id=dc) for nc in new_cats: PublisherCategoryMap.add(post_id=self.id, category_id=nc) def update_tags(self, tags_list): """ Update tags by replacing existing list with new list :param tags_list: list. The new list of tags """ tags = PublisherTagMap.all()\ .filter(PublisherTagMap.post_id == self.id) tags_list_ = [c.tag_id for c in tags] del_tags = list(set(tags_list_) - set(tags_list)) new_tags = list(set(tags_list) - set(tags_list_)) for dc in del_tags: PublisherTagMap.remove(post_id=self.id, tag_id=dc) for nc in new_tags: PublisherTagMap.add(post_id=self.id, tag_id=nc) def get_list(self): if not self.is_list: return None return PublisherPost.all()\ .filter(PublisherPost.is_published == True)\ .filter(PublisherPost.is_child == True)\ .filter(PublisherPost.parent_id == self.id) def delete_revisions(self): """ Delete all revisions """ try: PublisherPost.all()\ .filter(PublisherPost.post_id == self.id)\ .filter(PublisherPost.is_revision == True)\ .delete() PublisherPost.db.commit() except Exception as ex: PublisherPost.db.rollback() def set_options(self, key, values): options = self.options options.update({key: values}) self.update(options_data=json.dumps(options)) @property def options(self): return json.loads(self.options_data) if self.options_data else {} @property def excerpt(self): """ Return description as excerpt, if empty, it will return the first paragraph :return: str """ if self.description: return self.description else: return "" @property def top_image(self): """ Return the top image Return the image url if exists, or it will get the first image Will get the first image from the markdown """ if self.featured_image: return self.featured_image elif self.content: md_images = markdown_ext.extract_images(self.content) return md_images[0] if md_images else None @property def status(self): if self.is_published: return "Published" elif self.is_draft: return "Draft" elif self.is_revision: return "Revision" else: return "" @property def total_revisions(self): return PublisherPost.all()\ .filter(PublisherPost.post_id == self.id)\ .filter(PublisherPost.is_revision == True)\ .count() class PublisherUploadObject(db.Model): parent_id = db.Column(db.Integer, index=True) user_id = db.Column(db.Integer, index=True) provider = db.Column(db.String(255)) container = db.Column(db.String(255)) local_path = db.Column(db.Text) name = db.Column(db.Text) description = db.Column(db.String(255)) size = db.Column(db.Integer) extension = db.Column(db.String(10), index=True) type = db.Column(db.String(25), index=True) object_path = db.Column(db.Text) object_url = db.Column(db.Text) is_private = db.Column(db.Boolean, index=True, default=False) return utils.to_struct(Post=PublisherPost, Category=PublisherCategory, Type=PublisherType, CategoryMap=PublisherCategoryMap, Tag=PublisherTag, TagMap=PublisherTagMap, UploadObject=PublisherUploadObject)
def model(db): class UserRole(db.Model): name = db.Column(db.String(75), index=True) level = db.Column(db.Integer, index=True) @classmethod def new(cls, name, level): name = utils.slugify(name) role = cls.get_by_name(name) if not role: role = cls.create(name=name, level=level) return role @classmethod def get_by_name(cls, name): name = utils.slugify(name) return cls.all().filter(cls.name == name).first() @classmethod def get_by_level(cls, level): return cls.all().filter(cls.level == level).first() class User(db.Model, UserMixin): role_id = db.Column(db.Integer, db.ForeignKey(UserRole.id)) email = db.Column(db.String(75), index=True, unique=True) email_confirmed = db.Column(db.Boolean, default=False) password_hash = db.Column(db.String(255)) has_temp_login = db.Column(db.Boolean, default=False) temp_login_token = db.Column(db.String(100), index=True) temp_login_expiration = db.Column(db.DateTime) first_name = db.Column(db.String(255)) last_name = db.Column(db.String(255)) date_of_birth = db.Column(db.Date) sex = db.Column( db.String(10) ) # To get confusion out of the way, Sex refers to natural/biological features. profile_image_url = db.Column(db.String(255)) signup_method = db.Column(db.String(255)) active = db.Column(db.Boolean, default=True, index=True) last_login = db.Column(db.DateTime) last_visited = db.Column(db.DateTime) role = db.relationship(UserRole) # ------ FLASK-LOGIN REQUIRED METHODS ---------------------------------- @property def is_active(self): return self.active # ---------- END FLASK-LOGIN REQUIREMENTS ------------------------------ @classmethod def get_by_email(cls, email): """ Return a User by email address """ return cls.all().filter(cls.email == email).first() @classmethod def get_by_temp_login(cls, token): """ Return a User by temp_login_token temp_login_token allows a user to login with the token and reset the password """ user = cls.all().filter(cls.temp_login_token == token).first() if user: now = datetime.datetime.now() if user.has_temp_login is True \ and user.temp_login_expiration > now: return user user.clear_temp_login() return None @classmethod def get_by_oauth(cls, provider, provider_user_id): """ Get a user by OAuth :param provider: :param provider_user_id: :return: User """ oauth = UserOauthLogin.get_by_provider( provider=provider, provider_user_id=provider_user_id) return oauth.user if oauth else None @classmethod def new(cls, email, password=None, first_name=None, last_name=None, role="USER", signup_method="email", profile_image_url=None, **kwargs): """ Create a new user account """ user = cls.get_by_email(email) if user: raise ModelError("User exists already") user = cls.create(email=email, first_name=first_name, last_name=last_name, signup_method=signup_method, profile_image_url=profile_image_url) if password: user.set_password(password) if role: role_ = UserRole.get_by_name(role.upper()) if role_: user.update(role_id=role_.id) return user @property def full_name(self): """ Return the full name :return: """ return "%s %s" % (self.first_name, self.last_name) @property def name(self): """ Alias to first_name :return: """ return self.first_name def password_matched(self, password): """ Check if the password matched the hash :returns bool: """ return utils.verify_encrypted_string(password, self.password_hash) def set_password(self, password, random=False): """ Encrypt the password and save it in the DB Return the password passed or the new password if randomed """ if random: password = utils.generate_random_string() self.update(password_hash=utils.encrypt_string(password)) return password def set_temp_login(self, expiration=60): """ Create temp login. It will allow to have change password on account :param expiration: in minutes the time for expiration """ expiration = datetime.datetime.now() + datetime.timedelta( minutes=expiration) while True: token = utils.generate_random_string(32).lower() if not User.all().filter( User.temp_login_token == token).first(): break self.update(has_temp_login=True, temp_login_token=token, temp_login_expiration=expiration) return token def clear_temp_login(self): self.update(has_temp_login=False, temp_login_token=None, temp_login_expiration=None) def add_oauth(self, provider, provider_user_id, **kwargs): """ To attach a user account to an OAUTH login :param provider: the name of the provider :param provider_user_id: the id :param kwargs: :return: Return UserOauthLogin """ u = UserOauthLogin.get_by_provider( provider=provider, provider_user_id=provider_user_id) if u: return u return UserOauthLogin.create(user_id=self.id, provider=provider, provider_user_id=provider_user_id, **kwargs) def has_any_roles(self, *roles): """ Check if user has any of the roles requested :param roles: tuple of roles string :return: bool """ roles = list(map(utils.slugify, list(roles))) for r in UserRole.all().filter(UserRole.name.in_(roles)): if r.id == self.role_id: return True return False class UserOauth(db.Model): user_id = db.Column(db.Integer, db.ForeignKey(User.id, ondelete='CASCADE')) provider = db.Column(db.String(50), index=True) provider_user_id = db.Column(db.String(255)) name = db.Column(db.String(255)) email = db.Column(db.String(255)) profile_image_url = db.Column(db.String(255)) access_token = db.Column(db.String(255)) access_key_id = db.Column(db.String(255)) access_secret_key = db.Column(db.String(255)) link = db.Column(db.String(255)) user = db.relationship(User, backref="oauth_logins") @classmethod def get_by_provider(cls, provider, provider_user_id): """ Returns the entry of the provider and user id :params provider: str - the provider name :params provider_user_id: """ return cls.all()\ .filter(cls.provider == provider)\ .filter(cls.provider_user_id == provider_user_id)\ .first() return utils.to_struct(User=User, Role=UserRole, OAuth=UserOauth)
def model(db): class UserRole(db.Model): name = db.Column(db.String(75), index=True) level = db.Column(db.Integer, index=True) @classmethod def new(cls, name, level): name = utils.slugify(name) role = cls.get_by_name(name) if not role: role = cls.create(name=name, level=level) return role @classmethod def get_by_name(cls, name): name = utils.slugify(name) return cls.all().filter(cls.name == name).first() @classmethod def get_by_level(cls, level): return cls.all().filter(cls.level == level).first() class User(db.Model, UserMixin): role_id = db.Column(db.Integer, db.ForeignKey(UserRole.id)) email = db.Column(db.String(75), index=True, unique=True) email_confirmed = db.Column(db.Boolean, default=False) password_hash = db.Column(db.String(255)) has_temp_login = db.Column(db.Boolean, default=False) temp_login_token = db.Column(db.String(100), index=True) temp_login_expiration = db.Column(db.DateTime) first_name = db.Column(db.String(255)) last_name = db.Column(db.String(255)) date_of_birth = db.Column(db.Date) sex = db.Column(db.String(10)) # To get confusion out of the way, Sex refers to natural/biological features. profile_image_url = db.Column(db.String(255)) signup_method = db.Column(db.String(255)) active = db.Column(db.Boolean, default=True, index=True) last_login = db.Column(db.DateTime) last_visited = db.Column(db.DateTime) role = db.relationship(UserRole) # ------ FLASK-LOGIN REQUIRED METHODS ---------------------------------- @property def is_active(self): return self.active # ---------- END FLASK-LOGIN REQUIREMENTS ------------------------------ @classmethod def get_by_email(cls, email): """ Return a User by email address """ return cls.all().filter(cls.email == email).first() @classmethod def get_by_temp_login(cls, token): """ Return a User by temp_login_token temp_login_token allows a user to login with the token and reset the password """ user = cls.all().filter(cls.temp_login_token == token).first() if user: now = datetime.datetime.now() if user.has_temp_login is True \ and user.temp_login_expiration > now: return user user.clear_temp_login() return None @classmethod def get_by_oauth(cls, provider, provider_user_id): """ Get a user by OAuth :param provider: :param provider_user_id: :return: User """ oauth = UserOauthLogin.get_by_provider(provider=provider, provider_user_id=provider_user_id) return oauth.user if oauth else None @classmethod def new(cls, email, password=None, first_name=None, last_name=None, role="USER", signup_method="email", profile_image_url=None, **kwargs): """ Create a new user account """ user = cls.get_by_email(email) if user: raise ModelError("User exists already") user = cls.create(email=email, first_name=first_name, last_name=last_name, signup_method=signup_method, profile_image_url=profile_image_url) if password: user.set_password(password) if role: role_ = UserRole.get_by_name(role.upper()) if role_: user.update(role_id=role_.id) return user @property def full_name(self): """ Return the full name :return: """ return "%s %s" % (self.first_name, self.last_name) @property def name(self): """ Alias to first_name :return: """ return self.first_name def password_matched(self, password): """ Check if the password matched the hash :returns bool: """ return utils.verify_encrypted_string(password, self.password_hash) def set_password(self, password, random=False): """ Encrypt the password and save it in the DB Return the password passed or the new password if randomed """ if random: password = utils.generate_random_string() self.update(password_hash=utils.encrypt_string(password)) return password def set_temp_login(self, expiration=60): """ Create temp login. It will allow to have change password on account :param expiration: in minutes the time for expiration """ expiration = datetime.datetime.now() + datetime.timedelta(minutes=expiration) while True: token = utils.generate_random_string(32).lower() if not User.all().filter(User.temp_login_token == token).first(): break self.update(has_temp_login=True, temp_login_token=token, temp_login_expiration=expiration) return token def clear_temp_login(self): self.update(has_temp_login=False, temp_login_token=None, temp_login_expiration=None) def add_oauth(self, provider, provider_user_id, **kwargs): """ To attach a user account to an OAUTH login :param provider: the name of the provider :param provider_user_id: the id :param kwargs: :return: Return UserOauthLogin """ u = UserOauthLogin.get_by_provider(provider=provider, provider_user_id=provider_user_id) if u: return u return UserOauthLogin.create(user_id=self.id, provider=provider, provider_user_id=provider_user_id, **kwargs) def has_any_roles(self, *roles): """ Check if user has any of the roles requested :param roles: tuple of roles string :return: bool """ roles = map(utils.slugify, list(roles)) for r in UserRole.all().filter(UserRole.name.in_(roles)): if r.id == self.role_id: return True return False class UserOauth(db.Model): user_id = db.Column(db.Integer, db.ForeignKey(User.id, ondelete='CASCADE')) provider = db.Column(db.String(50), index=True) provider_user_id = db.Column(db.String(255)) name = db.Column(db.String(255)) email = db.Column(db.String(255)) profile_image_url = db.Column(db.String(255)) access_token = db.Column(db.String(255)) access_key_id = db.Column(db.String(255)) access_secret_key = db.Column(db.String(255)) link = db.Column(db.String(255)) user = db.relationship(User, backref="oauth_logins") @classmethod def get_by_provider(cls, provider, provider_user_id): """ Returns the entry of the provider and user id :params provider: str - the provider name :params provider_user_id: """ return cls.all()\ .filter(cls.provider == provider)\ .filter(cls.provider_user_id == provider_user_id)\ .first() return utils.to_struct(User=User, Role=UserRole, OAuth=UserOauth)