SQLAlchemy mixin classes¶
Coaster provides a number of mixin classes for SQLAlchemy models. To use in your Flask app:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from coaster.sqlalchemy import BaseMixin
app = Flask(__name__)
db = SQLAlchemy(app)
class MyModel(BaseMixin, db.Model):
__tablename__ = 'my_model'
Mixin classes must always appear before db.Model
in your model’s base classes.
-
class
coaster.sqlalchemy.mixins.
IdMixin
[source]¶ Provides the
id
primary key column-
query_class
¶ alias of
coaster.sqlalchemy.comparators.Query
-
-
class
coaster.sqlalchemy.mixins.
TimestampMixin
[source]¶ Provides the
created_at
andupdated_at
audit timestamps-
query_class
¶ alias of
coaster.sqlalchemy.comparators.Query
-
-
class
coaster.sqlalchemy.mixins.
PermissionMixin
[source]¶ Provides the
permissions()
method used by BaseMixin and derived classes-
current_permissions
¶ InspectableSet
containing currently available permissions from this object, usingcurrent_auth
.
-
-
class
coaster.sqlalchemy.mixins.
UrlDict
(obj)[source]¶ Provides dictionary access to an object’s URLs.
-
class
coaster.sqlalchemy.mixins.
UrlForMixin
[source]¶ Provides a
url_for()
method used by BaseMixin-derived classes-
classview_for
(action='view')[source]¶ Return the classview that contains the viewhandler for the specified action
-
classmethod
is_url_for
(_action, _endpoint=None, _app=None, _external=None, **paramattrs)[source]¶ View decorator that registers the view as a
url_for()
target.Parameters: - _action (str) – Action to register a URL under
- _endpoint (str) – View endpoint name to pass to Flask’s
url_for
- _app – The app to register this action on (if your repo has multiple apps)
- _external – If True, URLs are assumed to be external-facing by default
- paramattrs (dict) – Mapping of URL parameter to attribute on the object
-
classmethod
register_endpoint
(action, endpoint, app=None, external=None, roles=None, paramattrs=None)[source]¶ Helper method for registering an endopint to a
url_for()
action.Parameters: - view_func – View handler to be registered
- action (str) – Action to register a URL under
- endpoint (str) – View endpoint name to pass to Flask’s
url_for
- app – Flask app (default: None)
- external – If True, URLs are assumed to be external-facing by default
- roles – Roles to which this URL is available, required by
UrlDict
- paramattrs (dict) – Mapping of URL parameter to attribute on the object
-
classmethod
register_view_for
(app, action, classview, attr)[source]¶ Register a classview and viewhandler for a given app and action
-
url_for
(action='view', **kwargs)[source]¶ Return public URL to this instance for a given action (default ‘view’).
-
url_for_endpoints
= {None: {}}¶ Mapping of {app: {action: UrlEndpointData}}, where attr is a string or tuple of strings. The same action can point to different endpoints in different apps. The app may also be None as fallback. Each subclass will get its own dictionary. This particular dictionary is only used as an inherited fallback.
-
urls
¶ Dictionary of URLs available on this object
-
view_for_endpoints
= {}¶ Mapping of {app: {action: (classview, attr)}}
-
-
class
coaster.sqlalchemy.mixins.
NoIdMixin
[source]¶ Mixin that combines all mixin classes except
IdMixin
, for use anywhere the timestamp columns and helper methods are required, but an id column is not.
-
class
coaster.sqlalchemy.mixins.
BaseMixin
[source]¶ Base mixin class for all tables that have an id column.
-
class
coaster.sqlalchemy.mixins.
BaseNameMixin
(*args, **kw)[source]¶ Base mixin class for named objects
Changed in version 0.5.0: If you used BaseNameMixin in your app before Coaster 0.5.0:
name
can no longer be a blank string in addition to being non-null. This is configurable and enforced with a SQL CHECK constraint, which needs a database migration:for tablename in ['named_table1', 'named_table2', ...]: # Drop CHECK constraint first in case it was already present op.drop_constraint(tablename + '_name_check', tablename) # Create CHECK constraint op.create_check_constraint( tablename + '_name_check', tablename, "name <> ''")
-
make_name
(reserved=())[source]¶ Autogenerates a
name
from thetitle
. If the auto-generated name is already in use in this model,make_name()
tries again by suffixing numbers starting with 2 until an available name is found.Parameters: reserved – List or set of reserved names unavailable for use
-
reserved_names
= []¶ Prevent use of these reserved names
-
title_for_name
¶ The version of the title used for
make_name()
-
-
class
coaster.sqlalchemy.mixins.
BaseScopedNameMixin
(*args, **kw)[source]¶ Base mixin class for named objects within containers. When using this, you must provide an model-level attribute “parent” that is a synonym for the parent object. You must also create a unique constraint on ‘name’ in combination with the parent foreign key. Sample use case in Flask:
class Event(BaseScopedNameMixin, db.Model): __tablename__ = 'event' organizer_id = db.Column(None, db.ForeignKey('organizer.id')) organizer = db.relationship(Organizer) parent = db.synonym('organizer') __table_args__ = (db.UniqueConstraint('organizer_id', 'name'),)
Changed in version 0.5.0: If you used BaseScopedNameMixin in your app before Coaster 0.5.0:
name
can no longer be a blank string in addition to being non-null. This is configurable and enforced with a SQL CHECK constraint, which needs a database migration:for tablename in ['named_table1', 'named_table2', ...]: # Drop CHECK constraint first in case it was already present op.drop_constraint(tablename + '_name_check', tablename) # Create CHECK constraint op.create_check_constraint( tablename + '_name_check', tablename, "name <> ''")
-
make_name
(reserved=())[source]¶ Autogenerates a
name
from thetitle
. If the auto-generated name is already in use in this model,make_name()
tries again by suffixing numbers starting with 2 until an available name is found.
-
permissions
(actor, inherited=None)[source]¶ Permissions for this model, plus permissions inherited from the parent.
-
reserved_names
= []¶ Prevent use of these reserved names
-
short_title
¶ Generates an abbreviated title by subtracting the parent’s title from this instance’s title.
-
title_for_name
¶ The version of the title used for
make_name()
-
-
class
coaster.sqlalchemy.mixins.
BaseIdNameMixin
(*args, **kw)[source]¶ Base mixin class for named objects with an id tag.
Changed in version 0.5.0: If you used BaseIdNameMixin in your app before Coaster 0.5.0:
name
can no longer be a blank string in addition to being non-null. This is configurable and enforced with a SQL CHECK constraint, which needs a database migration:for tablename in ['named_table1', 'named_table2', ...]: # Drop CHECK constraint first in case it was already present op.drop_constraint(tablename + '_name_check', tablename) # Create CHECK constraint op.create_check_constraint( tablename + '_name_check', tablename, "name <> ''")
-
make_name
()[source]¶ Autogenerates a
name
fromtitle_for_name
-
title_for_name
¶ The version of the title used for
make_name()
-
url_id_name
¶ Returns a URL name combining
url_id
andname
in id-name syntax. This property is also available asurl_name
for legacy reasons.
-
-
class
coaster.sqlalchemy.mixins.
BaseScopedIdMixin
(*args, **kw)[source]¶ Base mixin class for objects with an id that is unique within a parent. Implementations must provide a ‘parent’ attribute that is either a relationship or a synonym to a relationship referring to the parent object, and must declare a unique constraint between url_id and the parent. Sample use case in Flask:
class Issue(BaseScopedIdMixin, db.Model): __tablename__ = 'issue' event_id = db.Column(None, db.ForeignKey('event.id')) event = db.relationship(Event) parent = db.synonym('event') __table_args__ = (db.UniqueConstraint('event_id', 'url_id'),)
-
class
coaster.sqlalchemy.mixins.
BaseScopedIdNameMixin
(*args, **kw)[source]¶ Base mixin class for named objects with an id tag that is unique within a parent. Implementations must provide a ‘parent’ attribute that is a synonym to the parent relationship, and must declare a unique constraint between url_id and the parent. Sample use case in Flask:
class Event(BaseScopedIdNameMixin, db.Model): __tablename__ = 'event' organizer_id = db.Column(None, db.ForeignKey('organizer.id')) organizer = db.relationship(Organizer) parent = db.synonym('organizer') __table_args__ = (db.UniqueConstraint('organizer_id', 'url_id'),)
Changed in version 0.5.0: If you used BaseScopedIdNameMixin in your app before Coaster 0.5.0:
name
can no longer be a blank string in addition to being non-null. This is configurable and enforced with a SQL CHECK constraint, which needs a database migration:for tablename in ['named_table1', 'named_table2', ...]: # Drop CHECK constraint first in case it was already present op.drop_constraint(tablename + '_name_check', tablename) # Create CHECK constraint op.create_check_constraint( tablename + '_name_check', tablename, "name <> ''")
-
title_for_name
¶ The version of the title used for
make_name()
-
url_id_name
¶ Returns a URL name combining
url_id
andname
in id-name syntax
-
url_name
¶ Returns a URL name combining
url_id
andname
in id-name syntax
-
-
class
coaster.sqlalchemy.mixins.
CoordinatesMixin
[source]¶ Adds
latitude
andlongitude
columns with a shorthandcoordinates
property that returns both.-
coordinates
¶ Tuple of (latitude, longitude).
-
has_coordinates
¶ Return True if both latitude and longitude are present.
-
has_missing_coordinates
¶ Return True if one or both of latitude and longitude are missing.
-
-
class
coaster.sqlalchemy.mixins.
UuidMixin
[source]¶ Provides a
uuid
attribute that is either a SQL UUID column or an alias to the existingid
column if the class uses UUID primary keys. Also provides hybrid propertiesuuid_hex
,buid
anduuid_b58
that provide hex, URL-safe Base64 and Base58 representations of theuuid
column.-
buid
¶ Retain buid as a public attribute for backward compatibility
-
uuid_b58
¶ URL-friendly UUID representation, using Base58 with the Bitcoin alphabet
-
uuid_b64
¶ URL-friendly UUID representation, using URL-safe Base64 (BUID)
-
uuid_hex
¶ URL-friendly UUID representation as a hex string
-
-
class
coaster.sqlalchemy.mixins.
RoleMixin
[source]¶ Provides methods for role-based access control.
Subclasses must define a
__roles__
dictionary with roles and the attributes they have call, read and write access to:__roles__ = { 'role_name': { 'call': {'meth1', 'meth2'}, 'read': {'attr1', 'attr2'}, 'write': {'attr1', 'attr2'}, 'grant': {'rel1', 'rel2'}, }, }
The
grant
key works in reverse: if the actor is present in any of the attributes in the set, they are granted that role viaroles_for()
. Attributes must be SQLAlchemy relationships and can be scalar, a collection or dynamic.The
with_roles()
decorator is recommended over__roles__
.-
access_for
(roles=None, actor=None, anchors=(), datasets=None)[source]¶ Return a proxy object that limits read and write access to attributes based on the actor’s roles.
Warning
If the roles parameter is provided, it overrides discovery of the actor’s roles in both the current object and related objects. It should only be used when roles are pre-determined and related objects are not required.
Parameters: - roles (set) – Roles to limit access to (not recommended)
- actor – Limit access to this actor’s roles
- anchors – Retrieve additional roles from anchors
- datasets (tuple) – Limit enumeration to the attributes in the dataset
If a datasets sequence is provided, the first dataset is applied to the current object and subsequent datasets are applied to objects accessed via relationships. Datasets limit the attributes available via enumeration when the proxy is cast into a dict or JSON. This can be used to remove unnecessary data or bi-directional relationships, which JSON can’t handle.
Attributes must be specified in a
__datasets__
dictionary on the object:__datasets__ = { 'primary': {'uuid', 'name', 'title', 'children', 'parent'}, 'related': {'uuid', 'name', 'title'} }
Objects and related objects can be safely enumerated like this:
proxy = obj.access_for(user, datasets=('primary', 'related')) proxydict = dict(proxy) proxyjson = json.dumps(proxy) # This needs a custom JSON encoder
If a dataset includes an attribute the role doesn’t have access to, it will be skipped. If it includes a relationship for which no dataset is specified, it will be rendered as an empty dict.
-
actors_with
(roles, with_role=False)[source]¶ Return actors who have the specified roles on this object, as an iterator.
Uses: 1.
__roles__[role]['granted_by']
2.__roles__[role]['granted_via']
Subclasses of
RoleMixin
that have custom role granting logic inroles_for()
must provide a matchingactors_with()
implementation.Parameters:
-
current_access
(datasets=None)[source]¶ Wraps
access_for()
withcurrent_auth
to return a proxy for the currently authenticated user.Parameters: datasets (tuple) – Datasets to limit enumeration to
-
current_roles
¶ InspectableSet
containing currently available roles on this object, usingcurrent_auth
. Use in the view layer to inspect for a role being present:- if obj.current_roles.editor:
- pass
{% if obj.current_roles.editor %}…{% endif %}
This property is also available in
RoleAccessProxy
.Warning
current_roles maintains a cache for efficient use in a template where it may be consulted multiple times. It is therefore not safe to use before and after code that modifies role assignment. Use
roles_for()
instead, or use current_roles only after roles are changed.
-
roles_for
(actor=None, anchors=())[source]¶ Return roles available to the given
actor
oranchors
on this object. The data type for both parameters are intentionally undefined here. Subclasses are free to define them in any way appropriate. Actors and anchors are assumed to be valid.The role
all
is always granted. Ifactor
is specified, the roleauth
is granted. If not,anon
is granted.Subclasses overriding
roles_for()
must always callsuper()
to ensure they are receiving the standard roles. Recommended boilerplate:def roles_for(self, actor=None, anchors=()): roles = super().roles_for(actor, anchors) # 'roles' is a set. Add more roles here # ... return roles
-
-
class
coaster.sqlalchemy.mixins.
RegistryMixin
[source]¶ Adds common registries to a model.
Included:
forms
registry, for WTForms formsviews
registry for view classes and helper functionsfeatures
registry for feature availability test functions.
The forms registry passes the instance to the registered form as an
obj
keyword parameter. The other registries pass it as the first positional parameter.