Coverage for .tox/coverage/lib/python3.11/site-packages/wuttjamaican/db/model/base.py: 100%
34 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-31 19:12 -0600
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-31 19:12 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttJamaican -- Base package for Wutta Framework
5# Copyright © 2023-2025 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Base Models
26.. class:: Base
28 This is the base class for all :term:`data models <data model>` in
29 the :term:`app database`. You should inherit from this class when
30 defining custom models.
32 This class inherits from :class:`WuttaModelBase`.
33"""
35import sqlalchemy as sa
36from sqlalchemy import orm
37from sqlalchemy.ext.associationproxy import association_proxy
39from wuttjamaican.db.util import naming_convention, ModelBase, uuid_column
42class WuttaModelBase(ModelBase): # pylint: disable=too-few-public-methods
43 """
44 Base class for data models, from which :class:`Base` inherits.
46 Custom models should inherit from :class:`Base` instead of this
47 class.
48 """
50 @classmethod
51 def make_proxy(cls, main_class, extension, name, proxy_name=None):
52 """
53 Convenience method to declare an "association proxy" for the
54 main class, per the params.
56 For more info see
57 :doc:`sqlalchemy:orm/extensions/associationproxy`.
59 :param main_class: Reference to the "parent" model class, upon
60 which the proxy will be defined.
62 :param extension: Attribute name on the main class, which
63 references the extension record.
65 :param name: Attribute name on the extension class, which
66 provides the proxied value.
68 :param proxy_name: Optional attribute name on the main class,
69 which will reference the proxy. If not specified, ``name``
70 will be used.
72 As a simple example consider this model, which extends the
73 :class:`~wuttjamaican.db.model.auth.User` class. In
74 particular note the last line which is what we're documenting
75 here::
77 import sqlalchemy as sa
78 from sqlalchemy import orm
79 from wuttjamaican.db import model
81 class PoserUser(model.Base):
82 \""" Poser extension for User \"""
83 __tablename__ = 'poser_user'
85 uuid = model.uuid_column(sa.ForeignKey('user.uuid'), default=None)
86 user = orm.relationship(
87 model.User,
88 doc="Reference to the main User record.",
89 backref=orm.backref(
90 '_poser',
91 uselist=False,
92 cascade='all, delete-orphan',
93 doc="Reference to the Poser extension record."))
95 favorite_color = sa.Column(sa.String(length=100), nullable=False, doc=\"""
96 User's favorite color.
97 \""")
99 def __str__(self):
100 return str(self.user)
102 # nb. this is the method call
103 PoserUser.make_proxy(model.User, '_poser', 'favorite_color')
105 That code defines a ``PoserUser`` model but also defines a
106 ``favorite_color`` attribute on the main ``User`` class, such
107 that it can be used normally::
109 user = model.User(username='barney', favorite_color='green')
110 session.add(user)
112 user = session.query(model.User).filter_by(username='bambam').one()
113 print(user.favorite_color)
114 """
115 proxy = association_proxy(
116 extension, proxy_name or name, creator=lambda value: cls(**{name: value})
117 )
118 setattr(main_class, name, proxy)
121metadata = sa.MetaData(naming_convention=naming_convention)
123Base = orm.declarative_base(metadata=metadata, cls=WuttaModelBase)
126class Setting(Base): # pylint: disable=too-few-public-methods
127 """
128 Represents a :term:`config setting`.
129 """
131 __tablename__ = "setting"
133 name = sa.Column(
134 sa.String(length=255),
135 primary_key=True,
136 nullable=False,
137 doc="""
138 Unique name for the setting.
139 """,
140 )
142 value = sa.Column(
143 sa.Text(),
144 nullable=True,
145 doc="""
146 String value for the setting.
147 """,
148 )
150 def __str__(self):
151 return self.name or ""
154class Person(Base):
155 """
156 Represents a person.
158 The use for this table in the base framework, is to associate with
159 a :class:`~wuttjamaican.db.model.auth.User` to provide first and
160 last name etc. (However a user does not have to be associated
161 with any person.)
163 But this table could also be used as a basis for a Customer or
164 Employee relationship etc.
165 """
167 __tablename__ = "person"
168 __versioned__ = {}
169 __wutta_hint__ = {
170 "model_title": "Person",
171 "model_title_plural": "People",
172 }
174 uuid = uuid_column()
176 full_name = sa.Column(
177 sa.String(length=100),
178 nullable=False,
179 doc="""
180 Full name for the person. Note that this is *required*.
181 """,
182 )
184 first_name = sa.Column(
185 sa.String(length=50),
186 nullable=True,
187 doc="""
188 The person's first name.
189 """,
190 )
192 middle_name = sa.Column(
193 sa.String(length=50),
194 nullable=True,
195 doc="""
196 The person's middle name or initial.
197 """,
198 )
200 last_name = sa.Column(
201 sa.String(length=50),
202 nullable=True,
203 doc="""
204 The person's last name.
205 """,
206 )
208 users = orm.relationship(
209 "User",
210 back_populates="person",
211 cascade_backrefs=False,
212 doc="""
213 List of :class:`~wuttjamaican.db.model.auth.User` accounts for
214 the person. Typically there is only one user account per
215 person, but technically multiple are supported.
216 """,
217 )
219 def __str__(self):
220 return self.full_name or ""
222 @property
223 def user(self):
224 """
225 Reference to the "first"
226 :class:`~wuttjamaican.db.model.auth.User` account for the
227 person, or ``None``.
229 .. warning::
231 Note that the database schema supports multiple users per
232 person, but this property logic ignores that and will only
233 ever return "one or none". That might be fine in 99% of
234 cases, but if multiple accounts exist for a person, the one
235 returned is indeterminate.
237 See :attr:`users` to access the full list.
238 """
240 # TODO: i'm not crazy about the ambiguity here re: number of
241 # user accounts a person may have. in particular it's not
242 # clear *which* user account would be returned, as there is no
243 # sequence ordinal defined etc. a better approach might be to
244 # force callers to assume the possibility of multiple
245 # user accounts per person? (if so, remove this property)
247 if self.users:
248 return self.users[0]
249 return None