def _prepare_association_table(table_name, remote1, remote2): return db.Table( table_name, db.metadata, db.Column(f"{remote1}_id", db.Integer, db.ForeignKey(f"{remote1}.id")), db.Column(f"{remote2}_id", db.Integer, db.ForeignKey(f"{remote2}.id")), )
def category_id(cls): if not hasattr(cls, "Category"): category_attrs = { "id": db.Column(db.Integer, primary_key=True), "objects": db.relationship(cls, backref="category"), } cls.Category = type(f"{cls.__name__}Category", (ClassifierMixin, db.Model), category_attrs) return db.Column(db.Integer, db.ForeignKey(cls.Category.id))
class Metadata(SQLAEvent): """Provides metadata about a specific piece of content.""" meta_title = db.Column( db.Unicode(255), default="", info=dict(label="Meta Title", description="The title used by search engines"), ) meta_description = db.Column( db.UnicodeText, default="", info=dict( label="Meta Description", description="The description used by search engines", ), ) keywords = db.Column( db.Text, default="", info=dict( label="Keywords", description= "The keywords for this content (Used by search engines)", ), ) should_auto_generate = db.Column( db.Boolean, info=dict( label="Auto generate the metadata", description="If enabled the metadata will be generated automaticly", ), ) def __get_meta_title__(self): return "" def __get_meta_description__(self): return "" def __get_keywords__(self): return "" def before_flush(self, session, is_modified): if self.should_auto_generate is None: self.should_auto_generate = True if not self.should_auto_generate: return cols = ["keywords", "meta_title", "meta_description"] state = db.inspect(self) for col in cols: state = db.inspect(self) if state.attrs[col].history.unchanged: continue func = getattr(self, f"__get_{col}__", None) if callable(func): setattr(self, col, func())
class Setting(DynamicProp, db.Model): __tablename__ = "setting" id = db.Column(db.Integer, primary_key=True) parent_id = db.Column(db.Integer, db.ForeignKey("settings.id")) category_id = db.Column(db.Integer, db.ForeignKey("setting_category.id")) cat = relationship("SettingCategory", backref="settings") category = association_proxy( "cat", "name", creator=lambda n: SettingCategory.get_or_create(name=n)) def __repr__(self): return "<Setting key={}><category:{}>".format(self.key, self.category)
class TimeStampped(SQLAEvent): """Add created and updated fields""" created = db.Column( db.DateTime, default=datetime.utcnow, nullable=False, info=dict(label="Creation Date"), ) updated = db.Column(db.DateTime, nullable=True, default=datetime.utcnow) def before_update(self, mapper, connection): if _request_ctx_stack.top is not None: self.updated = datetime.utcnow()
def tags(cls): if not hasattr(cls, "Tag"): # Create the Tag model tag_attrs = { "id": db.Column(db.Integer, primary_key=True), "objects": db.relationship( cls, secondary=lambda: cls.__tags_association_table__, backref="related_tags", ), } cls.Tag = type(f"{cls.__name__}Tag", (ClassifierMixin, db.Model), tag_attrs) # The many-to-many association table cls.__tags_association_table__ = _prepare_association_table( table_name=f"{cls.__tablename__}s_tags", remote1=cls.__tablename__, remote2=cls.Tag.__tablename__, ) return association_proxy( "related_tags", "title", creator=lambda t: cls.Tag.get_or_create(title=t))
def editor_id(cls): return db.Column( db.Integer, db.ForeignKey("user.id"), nullable=True, info=dict(label=lazy_gettext("Author")), )
class Page(AbstractPage): """The concrete Page model.""" __contenttype__ = "page" id = db.Column(db.Integer, db.Sequence("page_id_seq", start=1, increment=1), primary_key=True) query_class = PageQuery
def show_in_menu(cls): return db.Column( db.Boolean, default=True, info=dict( label="Show in menu", description="Show this page in the navigation menus.", ), )
class CommentMixin(SelfRelated): author_name = db.Column(db.String(255), nullable=False) author_email = db.Column(db.String(255), nullable=False) body = db.Column(db.Text) remote_addr = db.Column( db.String(255), nullable=False, default=lambda: request.remote_addr ) user_agent = db.Column( db.String(255), nullable=False, default=lambda: request.user_agent ) def __init__(self, author_name, author_email, body): self.author_name = author_name self.body = body if email(author_email): self.author_email = author_email else: raise ValueError(f"{author_email} is not a valid email address.")
def slug(cls): return db.Column( db.Unicode(255), info=dict( label="Slug", description= "A Slug is that portion of the URL used to identify this content.", ), )
class Ordered(SQLAEvent): """Provide an ordering field.""" sort_order = db.Column(db.Integer) __children_ordering_column__ = "sort_order" def after_flush_postexec(self, session, is_modified): if self.sort_order is None: self.sort_order = db.inspect(self).mapper.primary_key[0]
class Settings(ImmutableProxiedDictMixin, db.Model, SQLAEvent): id = db.Column(db.Integer, primary_key=True) store = db.relationship( "Setting", collection_class=attribute_mapped_collection("key")) _proxied = association_proxy("store", "value") profile_id = db.Column(db.Integer, db.ForeignKey("settings_profile.id"), nullable=False) def on_init(self): process_func = lambda seq: [Field(**d) for d in seq if type(d) is dict] ready = ((c, process_func(l)) for c, l in current_app.provided_settings) for category, opts in ready: for opt in opts: setting = Setting(key=opt.name) setting.value = opt.default setting.category = str(category) db.session.add(setting) self.store[opt.name] = setting
class Titled(SQLAEvent): """Provide a mandatory title field""" title = db.Column( db.Unicode(512), nullable=False, info=dict(label="Title", description="The title to display for the end user."), ) def __str__(self): return self.title
class SettingsProfile(SQLAEvent, db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column( db.Unicode(255), unique=True, nullable=False, info=dict( label="Profile Name:", description="A Unique name for this settings profile.", ), ) is_active = db.Column( db.Boolean, default=False, info=dict( label="Active", description= "Sets or unsets this settings profile as the default profile", ), ) settings = db.relationship("Settings", backref="profile", uselist=False) def on_init(self): if self.settings is None: self.settings = Settings() def after_flush_postexec(self, session, is_modified): if self.is_active: thistbl = get_owning_table(self, "is_active") up = (db.update(thistbl).where( db.and_(thistbl.c.id != self.id, thistbl.c.is_active == True)).values(is_active=False)) db.session.execute(up) def __str__(self): return self.name def __repr__(self): return "SettingsProfile(name='{}')".format(self.name)
class Profile(ImmutableProxiedDictMixin, db.Model, TimeStampped): id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer, db.ForeignKey("user.id"), unique=True, nullable=False, info=dict(label=lazy_gettext("User")), ) user = db.relationship( User, backref=backref("profile", uselist=False, cascade="all, delete-orphan"), single_parent=True, info=dict(label=lazy_gettext("User"), description=lazy_gettext("")), ) extras = db.relationship( "ProfileExtras", collection_class=attribute_mapped_collection("key") ) _proxied = association_proxy("extras", "value") def __repr__(self): return f"<{self.user.user_name}: Profile()>"
class User(db.Model, UserMixin, SQLAEvent): id = db.Column(db.Integer, primary_key=True) user_name = db.Column( db.Unicode(128), nullable=False, unique=True, index=True, info=dict( label=lazy_gettext("User Name"), description=lazy_gettext( "A unique name for this user (used for login)"), ), ) email = db.Column( db.String(255), unique=True, info=dict(label=lazy_gettext("Email"), description=lazy_gettext("")), ) password = db.Column( db.String(255), info=dict(label=lazy_gettext("Password"), description=lazy_gettext("")), ) active = db.Column( db.Boolean, info=dict( label=lazy_gettext("Active"), description=lazy_gettext( "Activate or deactivate this user account"), ), ) confirmed_at = db.Column(db.DateTime(), info=dict(label=lazy_gettext("Confirmed At"))) roles = db.relationship( "Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic"), info=dict(label=lazy_gettext("Roles"), description=lazy_gettext("")), ) # TODO: remove this one name = property(fget=lambda self: self.profile.name) @validates("password") def validate_password(self, key, value): """TBD later""" return value @validates("email") def validate_email(self, key, value): if not is_valid_email(value): raise ValueError("Invalid email address for %r" % self) return value def __str__(self): return self.user_name def __repr__(self): return "User(user_name={})".format(self.user_name)
class Role(db.Model, RoleMixin): id = db.Column(db.Integer(), primary_key=True) name = db.Column( db.String(80), unique=True, info=dict( label=lazy_gettext("Name"), description=lazy_gettext("A name to identify this role"), ), ) description = db.Column( db.Unicode(255), info=dict( label=lazy_gettext("Description"), description=lazy_gettext("A simple summary about this role."), ), ) def __str__(self): return self.name def __repr__(self): return f"<Role(name='{self.name}'>"
def comments(cls): if not hasattr(cls, "Comment"): comment_attrs = {"id": db.Column(db.Integer, primary_key=True)} cls.Comment = type( f"{cls.__name__}Comment", (CommentMixin, db.Model), comment_attrs ) # The many-to-many association table cls.__comments_association_table__ = _prepare_association_table( table_name=f"{cls.__tablename__}s_comments", remote1=cls.__tablename__, remote2=cls.Comment.__tablename__, ) return db.relationship( cls.Comment, secondary=cls.__comments_association_table__, backref="objects" )
class AbstractPage(NodeSpec, Displayable, BaseNestedSets, ScopedUniquelySlugged): """Extends :class:`Displayable` with special fields""" __abstract__ = True url_path = db.Column(db.String(1024), unique=True, nullable=True, index=True) @hybrid_property def url(self): return self.url_path @staticmethod def construct_url_path(page): slugs = [p.slug for p in page.path_to_root(order=db.asc)] return "/".join(slugs) def before_commit(self, session, is_modified): session.flush() state = db.inspect(self) tbl = state.mapper.primary_base_mapper.tables[0] rps = [self.slug] parent = self.parent while parent is not None: rps.append(parent.slug) parent = parent.parent rps.reverse() new_url_path = "/".join(rps) up = db.update(tbl).where(tbl.c.id == self.id).values( url_path=new_url_path) session.execute(up) session.refresh(self) with session.no_autoflush: for child in self.children: AbstractPage.before_commit(child, session, True) def __repr__(self): return f'<{self.__class__.__name__}(title="{self.title}")>'
class SettingCategory(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), nullable=False, unique=True) def __init__(self, name): self.name = name
def contenttype(cls): return db.Column(db.String(128), info=dict(label="Type"))
class ProfileExtras(DynamicProp, db.Model): id = db.Column(db.Integer, primary_key=True) profile_id = db.Column(db.Integer, db.ForeignKey("profile.id"))
def parent_id(cls): return db.Column(db.Integer, db.ForeignKey(cls.id), info=dict(label="Parent"))
class DynamicProp(PolymorphicVerticalProperty): key = db.Column(db.String(128), nullable=False) type = db.Column(db.String(64)) int_value = db.Column(db.Integer, info={"type": (int, "integer")}) str_value = db.Column(db.Unicode(5120), info={"type": (str, "string")}) bool_value = db.Column(db.Boolean, info={"type": (bool, "boolean")})
:license: MIT, see LICENSE for more details. """ from sqlalchemy import inspect from sqlalchemy.orm import validates from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.ext.associationproxy import association_proxy from flask_security import UserMixin, RoleMixin from oy.boot.sqla import db from oy.babel import lazy_gettext from oy.helpers import is_valid_email from oy.models.mixins import TimeStampped, SQLAEvent roles_users = db.Table( "roles_users", db.Column("user_id", db.Integer(), db.ForeignKey("user.id")), db.Column("role_id", db.Integer(), db.ForeignKey("role.id")), ) class Role(db.Model, RoleMixin): id = db.Column(db.Integer(), primary_key=True) name = db.Column( db.String(80), unique=True, info=dict( label=lazy_gettext("Name"), description=lazy_gettext("A name to identify this role"), ), ) description = db.Column(