Configuring Ziggurat Foundations

Installation and initial migration

Install the package:

$ pip install ziggurat_foundations

You will also need to install one of supported bcrypt backends like bcrypt or py-bcrypt (this is default solution ziggurat_foundations expects).

$ pip install bcrypt

Now it’s time to initialize your model structure with alembic.

You will first need to install alembic:

$ pip install alembic>=0.7.0

After you obtain recent alembic you can now run your migrations against database of your choice.

Warning

It is critical that you use alembic for migrations, if you perform normal table creation like metadata.create_all() with sqlalchemy you will not be able to perform migrations if database schema changes for ziggurat and some constraints will be missing from your database even if things will appear to work fine for you.

First you will need to create alembic.ini file with following contents:

[alembic]
script_location = ziggurat_foundations:migrations
sqlalchemy.url = driver://user:pass@host/dbname

[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

then you can run migration command:

$ alembic upgrade head

At this point all your database structure should be prepared for usage.

Implementing ziggurat_foundations within your application

Warning

class names like User inside ziggurat_foundations.models namespace CAN NOT be changed because they are reused in various queries - unless you reimplement ziggurat_model_init

We need to include ALL mixins inside our application and map classes together so internal methods can function properly.

In order to use the mixins inside your application, you need to include the follwing code inside your models file, to extend your existing models (if following the basic pyramid tutorial):

# ... your DBSession and base gets created in your favourite framework ...

import ziggurat_foundations.models
from ziggurat_foundations.models.base import BaseModel
from ziggurat_foundations.models.external_identity import ExternalIdentityMixin
from ziggurat_foundations.models.group import GroupMixin
from ziggurat_foundations.models.group_permission import GroupPermissionMixin
from ziggurat_foundations.models.group_resource_permission import GroupResourcePermissionMixin
from ziggurat_foundations.models.resource import ResourceMixin
from ziggurat_foundations.models.user import UserMixin
from ziggurat_foundations.models.user_group import UserGroupMixin
from ziggurat_foundations.models.user_permission import UserPermissionMixin
from ziggurat_foundations.models.user_resource_permission import UserResourcePermissionMixin
from ziggurat_foundations import ziggurat_model_init

# this is needed for pylons 1.0 / akhet approach to db session
ziggurat_foundations.models.DBSession = DBSession
# optional for folks who pass request.db to model methods

# Base is sqlalchemy's Base = declarative_base() from your project
class Group(GroupMixin, Base):
    pass

class GroupPermission(GroupPermissionMixin, Base):
    pass

class UserGroup(UserGroupMixin, Base):
    pass

class GroupResourcePermission(GroupResourcePermissionMixin, Base):
    pass

class Resource(ResourceMixin, Base):
    # ... your own properties....

    # example implementation of ACLS for pyramid application
    @property
    def __acl__(self):
        acls = []

        if self.owner_user_id:
            acls.extend([(Allow, self.owner_user_id, ALL_PERMISSIONS,), ])

        if self.owner_group_id:
            acls.extend([(Allow, "group:%s" % self.owner_group_id,
                          ALL_PERMISSIONS,), ])
        return acls

class UserPermission(UserPermissionMixin, Base):
    pass

class UserResourcePermission(UserResourcePermissionMixin, Base):
    pass

class User(UserMixin, Base):
    # ... your own properties....
    pass

class ExternalIdentity(ExternalIdentityMixin, Base):
    pass

# you can define multiple resource derived models to build a complex
# application like CMS, forum or other permission based solution

class Entry(Resource):
    """
    Resource of `entry` type
    """

    __tablename__ = 'entries'
    __mapper_args__ = {'polymorphic_identity': 'entry'}

    resource_id = sa.Column(sa.Integer(),
                            sa.ForeignKey('resources.resource_id',
                                          onupdate='CASCADE',
                                          ondelete='CASCADE', ),
                            primary_key=True, )
    # ... your own properties....
    some_property = sa.Column(sa.UnicodeText())


ziggurat_model_init(User, Group, UserGroup, GroupPermission, UserPermission,
               UserResourcePermission, GroupResourcePermission, Resource,
               ExternalIdentity, passwordmanager=None)

Hint

Because some systems can’t utilize bcypt password manager you can pass your own passlib compatible password manager to ziggurat_model_init, it will be used instead of creating default one.