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

url_id[source]

The URL id, integer primary key rendered as a string

class coaster.sqlalchemy.mixins.TimestampMixin[source]

Provides the created_at and updated_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, using current_auth.

permissions(actor, inherited=None)[source]

Return permissions available to the given user on this object

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(action='view')[source]

Return the classview viewhandler that handles the specified action

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 <> ''")
classmethod get(name)[source]

Get an instance matching the name

make_name(reserved=())[source]

Autogenerates a name from the title. 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()

classmethod upsert(name, **fields)[source]

Insert or update an instance

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 <> ''")
classmethod get(parent, name)[source]

Get an instance matching the parent and name

make_name(reserved=())[source]

Autogenerates a name from the title. 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()

classmethod upsert(parent, name, **fields)[source]

Insert or update an instance

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 from title_for_name

title_for_name

The version of the title used for make_name()

url_id_name

Returns a URL name combining url_id and name in id-name syntax. This property is also available as url_name for legacy reasons.

url_name

Returns a URL name combining url_id and name in id-name syntax. This property is also available as url_name for legacy reasons.

url_name_uuid_b58

Returns a URL name combining name and uuid_b58 in name-uuid_b58 syntax. To use this, the class must derive from UuidMixin.

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'),)
classmethod get(parent, url_id)[source]

Get an instance matching the parent and url_id

make_id()[source]

Create a new URL id that is unique to the parent container

permissions(actor, inherited=None)[source]

Permissions for this model, plus permissions inherited from the parent.

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 <> ''")
classmethod get(parent, url_id)[source]

Get an instance matching the parent and name

make_name()[source]

Autogenerates a title from the name

title_for_name

The version of the title used for make_name()

url_id_name

Returns a URL name combining url_id and name in id-name syntax

url_name

Returns a URL name combining url_id and name in id-name syntax

url_name_uuid_b58

Returns a URL name combining name and uuid_b58 in name-uuid_b58 syntax. To use this, the class must derive from UuidMixin.

class coaster.sqlalchemy.mixins.CoordinatesMixin[source]

Adds latitude and longitude columns with a shorthand coordinates 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 existing id column if the class uses UUID primary keys. Also provides hybrid properties uuid_hex, buid and uuid_b58 that provide hex, URL-safe Base64 and Base58 representations of the uuid 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 via roles_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 in roles_for() must provide a matching actors_with() implementation.

Parameters:
  • roles (set) – Iterable specifying roles to find actors with. May be an ordered type if ordering is important
  • with_role (bool) – If True, yields a tuple of the actor and the role they were found with. The actor may have more roles, but only the first match is returned
current_access(datasets=None)[source]

Wraps access_for() with current_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, using current_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 or anchors 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. If actor is specified, the role auth is granted. If not, anon is granted.

Subclasses overriding roles_for() must always call super() 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 forms
  • views registry for view classes and helper functions
  • features 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.