Esempio n. 1
0
class UserInfo(Model['UserInfo'], metaclass=ModelMetaclass):
    #class UserModel(Model['UserModel']):
    """Auth User Model"""

    # Database connection and table information
    __tableclass__ = table.UserInfo

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='User Primary ID',
        #sortable=True,
        #searchable=True,
    )

    extra1: str = Field(
        'extra1',
        description='Extra1',
    )

    user_id: int = Field(
        'user_id',
        description="Contacts User ID",
        required=True,
    )

    # One-To-One Inverse - Contact has ONE User
    user: Optional[User] = Field(
        None,
        description="User Model",
        #belongs_to=('uvicore.auth.models.user.User', 'id', 'user_id'),
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'user_id')
        relation=BelongsTo('uvicore.auth.models.user.User'))
Esempio n. 2
0
class xx_ModelName(Model['xx_ModelName'], metaclass=ModelMetaclass):
    """xx_AppName xx_ModelName Model"""

    # Database table definition
    # Optional as some models have no database table
    __tableclass__ = table.xx_TableName

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='xx_ModelName ID',
        #sortable=True,
        #searchable=True,
        read_only=True,
    )

    slug: str = Field(
        'slug',
        description='URL Friendly xx_ModelName Title Slug',
        required=True,
    )

    title: str = Field(
        'title',
        description='xx_ModelName Title',
        required=True,
    )
Esempio n. 3
0
class Group(Model['Group'], metaclass=ModelMetaclass):
    """Auth Group Model"""

    __tableclass__ = table.Groups

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Group ID',
    )

    # key: str = Field('key',
    #     primary=True,
    #     description='Group Primary Key',
    # )

    name: str = Field(
        'name',
        description='Group Name',
        required=True,
    )

    # Many-To-Many via group_roles pivot table
    roles: Optional[List[Role]] = Field(
        None,
        description="Group Roles",
        relation=BelongsToMany('uvicore.auth.models.role.Role',
                               join_tablename='group_roles',
                               left_key='group_id',
                               right_key='role_id'),
    )
Esempio n. 4
0
class Space(Model['Space'], metaclass=ModelMetaclass):
    """Wiki Space Model"""

    # Database table definition
    __tableclass__ = table.Spaces

    id: Optional[int] = Field('id',
        primary=True,
        description='Space ID',
        read_only=True,
    )

    slug: str = Field('slug',
        description='URL Friendly Space Slug',
        required=True,
    )

    name: str = Field('name',
        description='Space Name',
        required=True,
    )

    order: int = Field('order',
        description='Space Display Order',
        required=True
    )

    # One-To-Many (One Space has Many Sections)
    sections: Optional[List[Section]] = Field(None,
        description='Space Sections Model',
        relation=HasMany('mreschke.wiki.models.Section', foreign_key='space_id')
    )
Esempio n. 5
0
class Format(Model['Format'], metaclass=ModelMetaclass):
    """Wiki Format Model"""

    # Database table definition
    __tableclass__ = table.Formats

    key: str = Field(
        'key',
        primary=True,
        description='Format Key',
    )

    name: str = Field(
        'name',
        description='Format name',
        required=True,
    )
Esempio n. 6
0
class Tag(Model['Tag'], metaclass=ModelMetaclass):
    """App1 Tags"""

    # Database table definition
    __tableclass__ = table.Tags

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Post ID',
        sortable=False,
        searchable=True,
        read_only=True,
    )

    name: str = Field(
        'name',
        description='Tag Name',
        required=True,
    )

    creator_id: int = Field(
        'creator_id',
        description="Tag Creator UserID",
        required=True,
    )

    # One-To-Many Inverse (One Post has One User)
    creator: Optional[User] = Field(
        None,
        description="Tag Creator User Model",

        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'creator_id'),
        relation=BelongsTo('uvicore.auth.models.user.User'),
    )

    posts: Optional[List[Post]] = Field(
        None,
        description="Tag Posts Model",
        relation=BelongsToMany('app1.models.post.Post',
                               join_tablename='post_tags',
                               left_key='tag_id',
                               right_key='post_id'),
    )
Esempio n. 7
0
class Comment(Model['Comment'], metaclass=ModelMetaclass):
    """App1 Post Comments"""

    # Database table definition
    __tableclass__ = table.Comments

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Comment ID',
        sortable=True,
        searchable=True,
        read_only=True,
    )

    title: str = Field(
        'title',
        description='Comment Title',
        required=True,
    )

    body: str = Field(
        'body',
        description='Comment Body',
    )

    post_id: int = Field(
        'post_id',
        description="Comment PostID",
        required=True,
    )

    # One-To-Many Inverse (One Comment has One Post)
    post: 'Optional[Post]' = Field(
        None,
        description="Comment Post Model",

        #belongs_to=('app1.models.post.Post', 'id', 'post_id'),
        #relation=BelongsTo('app1.models.post.Post', 'id', 'post_id'),
        relation=BelongsTo('app1.models.post.Post'),
    )

    creator_id: int = Field(
        'creator_id',
        description="Comment Creator UserID",
        required=True,
    )

    # One-To-Many Inverse (One Post has One Creator)
    creator: Optional[User] = Field(
        None,
        description="Comment Creator User Model",
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'creator_id'),
        relation=BelongsTo('uvicore.auth.models.user.User'),
    )

    def cb_results(self):
        return self.slug + ' callback'
Esempio n. 8
0
class Section(Model['Section'], metaclass=ModelMetaclass):
    """Wiki Section Model"""

    # Database table definition
    __tableclass__ = table.Sections

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Section ID',
        read_only=True,
    )

    slug: str = Field(
        'slug',
        description='URL Friendly Section Slug',
        required=True,
    )

    name: str = Field(
        'name',
        description='Section Name',
        required=True,
    )

    icon: str = Field(
        'icon',
        description='Section Icon',
        required=False,
    )

    order: int = Field('order',
                       description='Section Display Order',
                       required=True)

    space_id: int = Field(
        'space_id',
        description='Section SpaceID',
        required=True,
    )

    # One-To-Many (One Section has Many Topics)
    topics: 'Optional[List[Topic]]' = Field(None,
                                            description='Topics Model',
                                            relation=HasMany(
                                                'mreschke.wiki.models.Topic',
                                                foreign_key='section_id'))

    # One-To-Many Inverse (One Section has one Space)
    space: 'Optional[Space]' = Field(
        None,
        description="Section Space Model",
        relation=BelongsTo('mreschke.wiki.models.Space'))
Esempio n. 9
0
class Topic(Model['Topic'], metaclass=ModelMetaclass):
    """Wiki Topic Model"""

    # Database table definition
    __tableclass__ = table.Topics

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Topic ID',
        read_only=True,
    )

    slug: str = Field(
        'slug',
        description='URL Friendly Topic Slug',
        required=True,
    )

    name: str = Field(
        'name',
        description='Topic Name',
        required=True,
    )

    desc: str = Field(
        'desc',
        description='Topic Description',
        required=False,
    )

    icon: str = Field(
        'icon',
        description='Topic Icon',
        required=False,
    )

    order: int = Field('order',
                       description='Topic Display Order',
                       required=True)

    section_id: int = Field(
        'section_id',
        description='Topic SectionID',
        required=True,
    )

    # One-To-Many Inverse (One Section has one Space)
    section: 'Optional[Section]' = Field(
        None,
        description="Topic Section Model",
        relation=BelongsTo('mreschke.wiki.models.Section'))
Esempio n. 10
0
class Permission(Model['Permission'], metaclass=ModelMetaclass):
    """Auth Permission Model"""

    __tableclass__ = table.Permissions

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Permission ID',
    )

    entity: str = Field(
        'entity',
        description='Permission Entity',
        required=True,
    )

    name: str = Field(
        'name',
        description='Permission Name',
        required=True,
    )
Esempio n. 11
0
class Attribute(Model['Attribute'], metaclass=ModelMetaclass):
    """Polymorphic One-To-Many Attributes"""

    # Database table definition
    __tableclass__ = table.Attributes

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Attribute ID',
        read_only=True,
    )

    attributable_type: str = Field(
        'attributable_type',
        description='Polymorphic Attribute Type',
        required=True,
    )

    attributable_id: int = Field(
        'attributable_id',
        description="Polymorphic Attribute Types ID",
        required=True,
    )

    key: str = Field(
        'key',
        description='Attribute Key',
        required=True,
        json=True,
    )

    value: Any = Field(
        'value',
        description="Attribute Value",
        required=True,
    )
Esempio n. 12
0
class Hashtag(Model['Hashtag'], metaclass=ModelMetaclass):
    """App1 Hashtags"""

    # Database table definition
    __tableclass__ = table.Hashtags

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Hashtag ID',
        sortable=False,
        searchable=True,
        read_only=True,
    )

    name: str = Field(
        'name',
        description='Hashtag Name',
        required=True,
    )

    @staticmethod
    async def post2(entity: Hashtag):
        return entity
Esempio n. 13
0
class Image(Model['Image'], metaclass=ModelMetaclass):
    """App1 Polymorphic One-To-One Image"""

    # Database table definition
    __tableclass__ = table.Images

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Image ID',
        read_only=True,
    )

    imageable_type: str = Field(
        'imageable_type',
        description='Polymorphic Image Type',
        required=True,
    )

    imageable_id: int = Field(
        'imageable_id',
        description="Polymorphic Image Type ID",
        required=True,
    )

    filename: str = Field(
        'filename',
        description='Image filename (no path)',
        required=True,
    )

    size: int = Field(
        'size',
        description="Image size (in bytes)",
        required=True,
    )
Esempio n. 14
0
class Contact(Model['Contact'], metaclass=ModelMetaclass):
#class ContactModel(Model['ContactModel'], metaclass=ModelMetaclass):
#class ContactModel(Model['ContactModel']):
#class _Contact2:
    """App1 Contacts"""

    # Databas        u.contact.e table definition
    __tableclass__ = table.Contacts

    id: Optional[int] = Field('id',
        primary=True,
        description='Contact ID',
        #read_only=True,
    )

    name: str = Field('name',
        description='Contact Name (First and Last or Company)',
        required=True,
    )

    title: str = Field('title',
        description='Contact Title or Position',
    )

    address: str = Field('address',
        description='Contact Address',
    )

    phone: str = Field('phone',
        description='Contact Phone Number',
    )

    user_id: int = Field('user_id',
        description="Contacts User ID",
        required=True,
    )

    # One-To-One Inverse - Contact has ONE User
    user: '******' = Field(None,
        description="Contact User Model",

        #belongs_to=('uvicore.auth.models.user.User', 'id', 'user_id'),
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'user_id')
        relation=BelongsTo('uvicore.auth.models.user.User')
    )
Esempio n. 15
0
class Post(Model['Post'], metaclass=ModelMetaclass):
    """Wiki Posts"""

    # Database table definition
    __tableclass__ = table.Posts

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Post ID',
        read_only=True,
    )

    slug: str = Field(
        'slug',
        description='URL Friendly Post Title Slug',
        required=True,
    )

    title: str = Field(
        'title',
        description='Post Title',
        required=True,
    )

    body: str = Field(
        'body',
        description='Post Body',
    )

    format_key: str = Field(
        'format_key',
        description='Post Format Key',
        required=True,
    )

    format: Optional[Format] = Field(
        None,
        description='Post Format Model',
        read_only=True,
        relation=BelongsTo('mreschke.wiki.models.Format',
                           foreign_key='key',
                           local_key='format_key'),
    )

    topic_id: int = Field(
        'topic_id',
        description='Post Topic ID',
        required=True,
    )

    topic: Optional[Topic] = Field(
        None,
        description='Post Topic Model',
        relation=BelongsTo('mreschke.wiki.models.Topic'))

    view_count: int = Field(
        'view_count',
        description='Total Post Views',
        default=0,
    )

    deleted: bool = Field(
        'deleted',
        description='Post is Deleted',
        default=False,
    )

    hidden: bool = Field(
        'hidden',
        description='Post is Hidden',
        default=False,
    )

    # space_id: int = Field('space_id',
    #     description='Space ID',
    #     required=True,
    # )

    creator_id: int = Field(
        'creator_id',
        description='Post Creator UserID',
        required=True,
    )

    # One-To-Many Inverse (One Post has One Creator)
    creator: Optional[User] = Field(
        None,
        description="Post Creator User Model",
        read_only=True,
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'creator_id'),
        relation=BelongsTo('uvicore.auth.models.User'),
    )

    updator_id: int = Field(
        'updator_id',
        description='Post Updator UserID',
        required=True,
    )

    updator: Optional[User] = Field(
        None,
        description="Post Creator User Model",
        read_only=True,
        relation=BelongsTo('uvicore.auth.models.User'),
    )

    created_at: datetime = Field(
        'created_at',
        description='Post Created DateTime',
        read_only=True,
    )

    updated_at: datetime = Field(
        'updated_at',
        description='Post Updated DateTime',
        read_only=True,
    )

    indexed_at: Optional[datetime] = Field(
        'indexed_at',
        description='Post Last Indexed DateTime',
        #read_only=True,
    )
Esempio n. 16
0
class Post(Model['Post'], metaclass=ModelMetaclass):
    #class _PostModel(Model['PostModel'], PostInterface, metaclass=ModelMetaclass):
    #class _PostModel(Model['PostModel'], metaclass=ModelMetaclass):
    #class PostModel(Model['PostModel'], ModelInterface['PostModel'], metaclass=ModelMetaclass):
    #class PostModel(Model['PostModel']):
    """App1 Posts"""

    # Database table definition
    __tableclass__ = table.Posts

    id: Optional[int] = Field(
        'id',
        primary=True,
        description='Post ID',
        sortable=False,
        searchable=True,
        read_only=True,
        # properties={
        #     'test': 'hi'
        # }
    )

    slug: str = Field(
        'unique_slug',
        description='URL Friendly Post Title Slug',
        required=True,
        # properties={
        #     'stuff': 'hi',
        #     'stuff2': 'hi2',
        # }
    )

    title: str = Field(
        'title',
        description='Post Title',
        required=True,
    )

    body: str = Field(
        'body',
        description='Post Body',
    )

    other: str = Field(
        'other',
        description='Post Other',
    )

    cb: str = Field(None, callback='cb_results')

    creator_id: int = Field(
        'creator_id',
        description="Post Creator UserID",
        required=True,
    )

    # One-To-Many Inverse (One Post has One Creator)
    creator: Optional[User] = Field(
        None,
        description="Post Creator User Model",
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'creator_id'),
        relation=BelongsTo('uvicore.auth.models.user.User'),
    )

    owner_id: int = Field(
        'owner_id',
        description="Post Owner UserID",
        required=True,
    )

    # One-To-Many Inverse (One Post has One Owner)
    owner: Optional[User] = Field(
        None,
        description="Post Owner User Model",
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'owner_id'),
        relation=BelongsTo('uvicore.auth.models.user.User'),
    )

    # One-To-Many (One Post has Many Comments)
    comments: Optional[List[Comment]] = Field(
        None,
        description="Post Comments Model",

        #has_many=('app1.models.comment.Comment', 'post_id', 'id'),
        #relation=HasMany('app1.models.comment.Comment', 'post_id', 'id'),
        relation=HasMany('app1.models.comment.Comment', foreign_key='post_id'),
        #relation=HasMany('app1.models.comment.Comment'),
    )

    # Many-To-Many via post_tags pivot table
    tags: Optional[List[Tag]] = Field(
        None,
        description="Post Tags",
        relation=BelongsToMany('app1.models.tag.Tag',
                               join_tablename='post_tags',
                               left_key='post_id',
                               right_key='tag_id'),
    )

    # Polymorphic One-To-One image
    image: Optional[Image] = Field(None,
                                   description="Post Image",
                                   relation=MorphOne('app1.models.image.Image',
                                                     polyfix='imageable'))

    # Polymorphic One-To-Many Attributes
    #attributes: Optional[List[Attribute]] = Field(None,
    attributes: Optional[Dict] = Field(
        None,
        description="Post Attributes",
        relation=MorphMany('app1.models.attribute.Attribute',
                           polyfix='attributable',
                           dict_key='key',
                           dict_value='value')
        #relation=MorphMany('app1.models.attribute.Attribute', polyfix='attributable')
    )

    # Polymorphic Many-To-Many Hashtags
    hashtags: Optional[List[str]] = Field(
        None,
        description="Post Hashtags",
        # relation=MorphToMany(
        #     model='app1.models.hashtag.Hashtag',
        #     join_tablename='hashtaggables',
        #     polyfix='hashtaggable',
        #     right_key='hashtag_id'
        # ),
        relation=MorphToMany(
            model='app1.models.hashtag.Hashtag',
            join_tablename='hashtaggables',
            polyfix='hashtaggable',
            right_key='hashtag_id',
            #dict_key='id',
            #dict_value='name',
            #list_value='name',
        ),
    )

    def cb_results(self):
        return str(self.slug) + ' callback'

    async def _before_save(self):
        await super()._before_save()
Esempio n. 17
0
class Comment(Model['Comment'], metaclass=ModelMetaclass):
    """App1 Post Comments"""

    # class Config:
    #     #title = 'nballs1'
    #     schema_extra = {
    #         "example": {
    #             "where": {
    #                 "name": "Foo2",
    #             },
    #             "unlink": ['tags2', 'hashtags2'],
    #         },
    #     }

    # Database table definition
    __tableclass__ = table.Comments

    # Possible are
    # __tableclass__
    # __connection__ if null then derived from __tableclass__.connection
    # __tablename__ if null then derived from __tableclass__.name
    # __table__ if null then derived from __tableclass__.schema

    # Technically you could manually define __callbacks__ = {field: callback_method} but no point

    # __example__ = {
    #     "where": {
    #         "name": "Foo",
    #     },
    #     "unlink": ['tags', 'hashtags'],
    # }

    id: Optional[int] = Field('id',
        primary=True,
        description='Comment ID',
        #sortable=True,
        #searchable=True,
        read_only=True,
    )

    title: str = Field('title',
        description='Comment Title',
        required=True,
    )

    body: str = Field('body',
        description='Comment Body',
    )

    post_id: Optional[int] = Field('post_id',
        description="Comment PostID",
        #required=True,
    )

    # One-To-Many Inverse (One Comment has One Post)
    post: Optional[Post] = Field(None,
        description="Comment Post Model",

        #belongs_to=('app1.models.post.Post', 'id', 'post_id'),
        #relation=BelongsTo('app1.models.post.Post', 'id', 'post_id'),
        relation=BelongsTo('app1.models.post.Post'),
    )

    creator_id: int = Field('creator_id',
        description="Comment Creator UserID",
        required=True,
    )

    # One-To-Many Inverse (One Post has One Creator)
    creator: Optional[User] = Field(None,
        description="Comment Creator User Model",
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'creator_id'),
        relation=BelongsTo('uvicore.auth.models.user.User'),
    )


    def cb_results(self):
        return self.slug + ' callback'
Esempio n. 18
0
class User(AuthOverride, Model['User'], metaclass=ModelMetaclass):

    #class UserModel(Model['UserModel'], metaclass=ModelMetaclass):
    #class UserModel(Model['UserModel']):
    #class UserModel(Model['UserModel'], metaclass=ModelMetaclass):
    #class _User(Model, metaclass=ModelMetaclass):
    """Auth User Model"""

    # Database connection and table information
    __tableclass__ = table.Users

    # id: Optional[int] = Field('id',
    #     primary=True,
    #     description='User Primary ID',
    #     sortable=True,
    #     searchable=True,
    # )

    # email: str = Field('email',
    #     description='User Email and Username',
    #     required=True,
    # )

    # # One-To-One - User has ONE Contact
    # info: Optional[UserInfo] = Field(None,
    #     description='User Info Model',
    #     relation=HasOne('uvicore.auth.models.user_info.UserInfo', foreign_key='user_id'),
    # )

    # CUSTOM ###################################################################

    app1_extra: Optional[str] = Field(
        'app1_extra',
        description='Extra column on auth.users by app1',
        required=False,
    )

    # One-To-One - User has ONE Contact
    info: Optional[UserInfo] = Field(
        None,
        description='User Info Model',
        relation=HasOne('app1.models.user_info.UserInfo',
                        foreign_key='user_id'),
    )

    # One-To-One - User has ONE Contact
    contact: Optional[Contact] = Field(
        None,
        description='Users Contact Model',

        #has_one=('app1.models.contact.Contact', 'user_id', 'id'),
        #relation=HasOne('app1.models.contact.Contact', 'user_id', 'id'),
        relation=HasOne('app1.models.contact.Contact', foreign_key='user_id'),
        #relation=HasOne('app1.models.contact.Contact'),
    )

    # One-To-Many (One User has Many Posts)
    posts: Optional[List[Post]] = Field(
        None,
        description="Users Posts Model",

        #has_many=('app1.models.post.Post', 'creator_id', 'id'),
        #relation=HasMany('app1.models.post.Post', 'creator_id', 'id')
        relation=HasMany('app1.models.post.Post', foreign_key='creator_id')
        #relation=HasMany('app1.models.post.Post')
    )

    # Polymorphic One-To-One image
    image: Optional[Image] = Field(None,
                                   description="Post Image",
                                   relation=MorphOne('app1.models.image.Image',
                                                     polyfix='imageable'))
Esempio n. 19
0
class User(Model['User'], metaclass=ModelMetaclass):
    """Auth User Model"""

    # Database connection and table information
    __tableclass__ = table.Users

    id: Optional[int] = Field('id',
        primary=True,
        required=False,
        description='User Primary ID',
        #sortable=True,
        #searchable=True,
    )

    uuid: Optional[str] = Field('uuid',
        description='Custom Alternate UUID',
        required=False,
    )

    username: str = Field('username',
        description='User Login Username',
        required=True,
    )

    email: str = Field('email',
        description='User Email',
        required=True,
    )

    first_name: str = Field('first_name',
        description='User First Name',
        required=True,
    )

    last_name: str = Field('last_name',
        description='User Last Name',
        required=True,
    )

    title: Optional[str] = Field('title',
        description='User Title',
        required=False,
    )

    avatar_url: Optional[str] = Field('avatar_url',
        description='User Avatar URL',
        required=False,
    )

    password: Optional[str] = Field('password',
        description='User Last Name',
        required=False,
        read_only=False,
        write_only=True,
    )

    disabled: Optional[bool] = Field('disabled',
        description='User Disabled',
        required=False,
        default=False,
    )

    creator_id: int = Field('creator_id',
        description="User Creator UserID",
        required=True,
    )

    # One-To-Many Inverse (One Post has One Creator)
    creator: Optional[User] = Field(None,
        description="Post Creator User Model",
        #relation=BelongsTo('uvicore.auth.models.user.User', 'id', 'creator_id'),
        relation=BelongsTo('uvicore.auth.models.user.User'),
    )

    created_at: Optional[datetime] = Field('created_at',
        description='Created at Datetime',
        required=False,
        read_only=True,
    )

    updated_at: Optional[datetime] = Field('updated_at',
        description='Updated at Datetime',
        required=False,
        read_only=True,
    )

    login_at: Optional[datetime] = Field('login_at',
        description='Last Login Datetime',
        required=False,
    )

    # Many-To-Many via user_groups pivot table
    groups: Optional[List[Group]] = Field(None,
        description="User Groups",
        relation=BelongsToMany('uvicore.auth.models.group.Group', join_tablename='user_groups', left_key='user_id', right_key='group_id'),
    )

    # Many-To-Many via user_groups pivot table
    roles: Optional[List[Role]] = Field(None,
        description="User Roles",
        relation=BelongsToMany('uvicore.auth.models.role.Role', join_tablename='user_roles', left_key='user_id', right_key='role_id'),
    )

    async def _before_save(self) -> None:
        """Hook fired before record is saved (inserted or updated)"""
        await super()._before_save()

        # Convert password to hash if is plain text (works for first insert and updates)
        if self.password is not None and 'argon2' not in self.password:
            self.password = pwd.create(self.password)