save point (see note)
Added initial database schema and ability to install it, added init-db command to pyramid scaffold.
This commit is contained in:
parent
925cd30b96
commit
8ceb98baf4
29 changed files with 1116 additions and 191 deletions
|
@ -29,7 +29,7 @@
|
|||
|
||||
from edbob._version import __version__
|
||||
from edbob.core import *
|
||||
from edbob.times import *
|
||||
from edbob.time import *
|
||||
from edbob.files import *
|
||||
from edbob.modules import *
|
||||
from edbob.configuration import *
|
||||
|
|
|
@ -157,7 +157,7 @@ Try '%(name)s help <command>' for more help.""" % self
|
|||
|
||||
# Initialize everything...
|
||||
if not args.no_init:
|
||||
edbob.init(*(args.config_paths or []))
|
||||
edbob.init(self.name, *(args.config_paths or []))
|
||||
|
||||
# Command line logging flags should override config.
|
||||
if args.verbose:
|
||||
|
|
|
@ -32,52 +32,77 @@ from sqlalchemy.orm import sessionmaker
|
|||
import edbob
|
||||
|
||||
|
||||
__all__ = ['engines', 'engine', 'Session', 'metadata',
|
||||
'get_setting', 'save_setting']
|
||||
|
||||
inited = False
|
||||
engines = None
|
||||
engine = None
|
||||
Session = sessionmaker()
|
||||
metadata = None
|
||||
|
||||
|
||||
def init():
|
||||
def init(config):
|
||||
"""
|
||||
Called whenever ``'edbob.db'`` is configured to be auto-initialized.
|
||||
Initializes the database connection(s); called by :func:`edbob.init()` if
|
||||
config includes something like::
|
||||
|
||||
This function is responsible for establishing the primary database engine
|
||||
(a ``sqlalchemy.Engine`` instance, read from config), and extending the
|
||||
root ``edbob`` namespace with the ORM classes (``Person``, ``User``, etc.),
|
||||
as well as a few other things, e.g. ``engine``, ``Session`` and
|
||||
``metadata``.
|
||||
.. highlight:: ini
|
||||
|
||||
In addition to this, if a connection to the primary database can be
|
||||
obtained, it will be consulted to see which extensions are active within
|
||||
it. If any are found, edbob's ORM will be extended in-place accordingly.
|
||||
[edbob]
|
||||
init = ['edbob.db']
|
||||
|
||||
[edbob.db]
|
||||
sqlalchemy.urls = {
|
||||
'default': 'postgresql://user:pass@localhost/edbob,
|
||||
}
|
||||
|
||||
This function reads connection info from ``config`` and builds a dictionary
|
||||
or :class:`sqlalchemy.Engine` instances accordingly. It also extends the
|
||||
root ``edbob`` namespace with the ORM classes (:class:`edbob.Person`,
|
||||
:class:`edbob.User`, etc.), as well as a few other things
|
||||
(e.g. :attr:`edbob.engine`, :attr:`edbob.Session`, :attr:`edbob.metadata`).
|
||||
"""
|
||||
|
||||
config = edbob.config.get_dict('edbob.db')
|
||||
engine = engine_from_config(config)
|
||||
edbob.graft(edbob, locals(), 'engine')
|
||||
|
||||
Session.configure(bind=engine)
|
||||
edbob.graft(edbob, globals(), 'Session')
|
||||
|
||||
import edbob.db
|
||||
from edbob.db import classes
|
||||
from edbob.db import enum
|
||||
from edbob.db.model import get_metadata
|
||||
metadata = get_metadata(bind=engine)
|
||||
edbob.graft(edbob, locals(), 'metadata')
|
||||
|
||||
from edbob.db.mappers import make_mappers
|
||||
make_mappers(metadata)
|
||||
from edbob.db.extensions import extend_framework
|
||||
|
||||
from edbob.db.ext import extend_framework
|
||||
global inited, engines, engine, metadata
|
||||
|
||||
keys = config.get('edbob.db', 'sqlalchemy.keys')
|
||||
if keys:
|
||||
keys = keys.split()
|
||||
else:
|
||||
keys = ['default']
|
||||
|
||||
engines = {}
|
||||
cfg = config.get_dict('edbob.db')
|
||||
for key in keys:
|
||||
try:
|
||||
engines[key] = engine_from_config(cfg, 'sqlalchemy.%s.' % key)
|
||||
except KeyError:
|
||||
if key == 'default':
|
||||
try:
|
||||
engines[key] = engine_from_config(cfg)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
engine = engines.get('default')
|
||||
if engine:
|
||||
Session.configure(bind=engine)
|
||||
|
||||
metadata = get_metadata(bind=engine)
|
||||
make_mappers(metadata)
|
||||
extend_framework()
|
||||
|
||||
# Note that we extend the framework before we graft the 'classes' module
|
||||
# contents, since extensions may graft things to that module.
|
||||
import edbob.db.classes as classes
|
||||
edbob.graft(edbob, edbob.db)
|
||||
edbob.graft(edbob, classes)
|
||||
|
||||
# Same goes for the enum module.
|
||||
import edbob.db.enum as enum
|
||||
edbob.graft(edbob, enum)
|
||||
|
||||
# Add settings functions.
|
||||
edbob.graft(edbob, globals(), ('get_setting', 'save_setting'))
|
||||
inited = True
|
||||
|
||||
|
||||
def get_setting(name, session=None):
|
||||
|
|
|
@ -26,15 +26,48 @@
|
|||
``edbob.db.auth`` -- Authentication & Authorization
|
||||
"""
|
||||
|
||||
import bcrypt
|
||||
|
||||
from sqlalchemy.orm import object_session
|
||||
|
||||
from edbob.db.classes import Role, User
|
||||
import edbob
|
||||
from edbob.db import needs_session
|
||||
from edbob.db.classes import Permission, Role, User
|
||||
|
||||
|
||||
def get_administrator(session):
|
||||
class BcryptAuthenticator(edbob.Object):
|
||||
"""
|
||||
Returns a :class:`edbob.Role` instance representing the "Administrator"
|
||||
role, attached to the given ``session``.
|
||||
Authentication with py-bcrypt (Blowfish).
|
||||
"""
|
||||
|
||||
def populate_user(self, user, password):
|
||||
user.salt = bcrypt.gensalt()
|
||||
user.password = bcrypt.hashpw(password, user.salt)
|
||||
|
||||
def authenticate_user(self, user, password):
|
||||
return bcrypt.hashpw(password, user.salt) == user.password
|
||||
|
||||
|
||||
@needs_session
|
||||
def authenticate_user(session, username, password):
|
||||
"""
|
||||
Attempts to authenticate with ``username`` and ``password``. If successful,
|
||||
returns the :class:`edbob.User` instance; otherwise returns ``None``.
|
||||
"""
|
||||
|
||||
user = session.query(User).filter_by(username=username).first()
|
||||
if not user:
|
||||
return None
|
||||
auth = BcryptAuthenticator()
|
||||
if not auth.authenticate_user(user, password):
|
||||
return None
|
||||
return user
|
||||
|
||||
|
||||
def administrator_role(session):
|
||||
"""
|
||||
Returns the "Administrator" :class:`edbob.Role` instance, attached to the
|
||||
given ``session``.
|
||||
"""
|
||||
|
||||
uuid = 'd937fa8a965611dfa0dd001143047286'
|
||||
|
@ -59,7 +92,7 @@ def has_permission(obj, perm):
|
|||
elif isinstance(obj, Role):
|
||||
roles = [obj]
|
||||
else:
|
||||
raise TypeError, "You must pass either a User or Role for 'obj'; got: %s" % repr(obj)
|
||||
raise TypeError("You must pass either a User or Role for 'obj'; got: %s" % repr(obj))
|
||||
session = object_session(obj)
|
||||
assert session
|
||||
admin = get_administrator(session)
|
||||
|
|
36
edbob/db/enum.py
Normal file
36
edbob/db/enum.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.db.enum`` -- Enumerations
|
||||
"""
|
||||
|
||||
|
||||
USER_ACTIVE = 1
|
||||
USER_INACTIVE = 2
|
||||
|
||||
USER_STATUS = {
|
||||
USER_ACTIVE : "active",
|
||||
USER_INACTIVE : "inactive",
|
||||
}
|
65
edbob/db/exceptions.py
Normal file
65
edbob/db/exceptions.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.db.exceptions`` -- Database Exceptions
|
||||
"""
|
||||
|
||||
|
||||
class CoreSchemaAlreadyInstalled(Exception):
|
||||
"""
|
||||
Raised when a request is made to install the core schema to a database, but
|
||||
it is already installed there.
|
||||
"""
|
||||
|
||||
def __init__(self, installed_version):
|
||||
self.installed_version = installed_version
|
||||
|
||||
def __str__(self):
|
||||
return "Core schema already installed (version %s)" % self.installed_version
|
||||
|
||||
|
||||
class CoreSchemaNotInstalled(Exception):
|
||||
"""
|
||||
Raised when a request is made which requires the core schema to be present
|
||||
in a database, yet such is not the case.
|
||||
"""
|
||||
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def __str__(self):
|
||||
return "Core schema not installed: %s" % str(self.engine)
|
||||
|
||||
|
||||
class ExtensionNotFound(Exception):
|
||||
"""
|
||||
Raised when an extension is requested which cannot be located.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return "Extension not found: %s" % self.name
|
426
edbob/db/extensions.py
Normal file
426
edbob/db/extensions.py
Normal file
|
@ -0,0 +1,426 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.db.extensions`` -- Database Extensions
|
||||
"""
|
||||
|
||||
import logging
|
||||
# from pkg_resources import iter_entry_points
|
||||
|
||||
import sqlalchemy.exc
|
||||
# from sqlalchemy.orm import clear_mappers
|
||||
|
||||
# import migrate.versioning.api
|
||||
# from migrate.versioning.schema import ControlledSchema
|
||||
|
||||
# import rattail
|
||||
# from rattail.db import exc as exceptions
|
||||
# from rattail.db import Session
|
||||
# from rattail.db.classes import ActiveExtension
|
||||
# from rattail.db.mappers import make_mappers
|
||||
# from rattail.db.model import get_metadata
|
||||
# from rattail.db.util import get_repository_path, get_repository_version
|
||||
|
||||
import edbob
|
||||
import edbob.db
|
||||
from edbob.db import exceptions
|
||||
from edbob.db import Session
|
||||
from edbob.db.classes import ActiveExtension
|
||||
from edbob.db.util import get_database_version
|
||||
from edbob.util import requires_impl
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
available_extensions = edbob.entry_point_map('edbob.db.extensions')
|
||||
active_extensions = {}
|
||||
|
||||
|
||||
class Extension(edbob.Object):
|
||||
"""
|
||||
Base class for schema/ORM extensions.
|
||||
"""
|
||||
|
||||
# Set this to a list of strings (extension names) as needed within your
|
||||
# derived class.
|
||||
required_extensions = []
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def name(self):
|
||||
"""
|
||||
The name of the extension.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def schema(self):
|
||||
"""
|
||||
Should return a reference to the extension's ``schema`` module, which
|
||||
is assumed to be a SQLAlchemy-Migrate repository.
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_class(self, cls):
|
||||
"""
|
||||
Convenience method for use in :meth:`extend_classes()`.
|
||||
"""
|
||||
|
||||
from edbob.db import classes
|
||||
|
||||
name = cls.__name__
|
||||
edbob.graft(classes, {name:cls}, name)
|
||||
|
||||
def extend_classes(self):
|
||||
"""
|
||||
Any extra classes provided by the extension should be added to the ORM
|
||||
whenever this method is called.
|
||||
|
||||
Note that the :meth:`add_class()` convenience method is designed to be
|
||||
used when adding classes.
|
||||
"""
|
||||
pass
|
||||
|
||||
def extend_mappers(self, metadata):
|
||||
"""
|
||||
All SQLAlchemy mapping to be done by the extension should be done
|
||||
within this method.
|
||||
|
||||
Any extra classes the extension provides will typically be mapped here.
|
||||
Any manipulation the extension needs to perform on the ``edbob`` core
|
||||
ORM should be done here as well.
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_metadata(self):
|
||||
"""
|
||||
Should return a :class:`sqlalchemy.MetaData` instance containing the
|
||||
schema definition for the extension, or ``None``.
|
||||
"""
|
||||
|
||||
return None
|
||||
|
||||
def remove_class(self, name):
|
||||
"""
|
||||
Convenience method for use in :meth:`restore_classes()`.
|
||||
"""
|
||||
|
||||
from edbob.db import classes
|
||||
|
||||
if name in classes.__all__:
|
||||
classes.__all__.remove(name)
|
||||
if hasattr(classes, name):
|
||||
del classes.__dict__[name]
|
||||
|
||||
def restore_classes(self):
|
||||
"""
|
||||
This method should remove any extra classes which were added within
|
||||
:meth:`extend_classes()`. Note that there is a :meth:`remove_class()`
|
||||
method for convenience in doing so.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# def activate_extension(extension, engine=None):
|
||||
# """
|
||||
# Activates the :class:`RattailExtension` instance represented by
|
||||
# ``extension`` (which can be the actual instance, or the extension's name)
|
||||
# by installing its schema and registering it within the database, and
|
||||
# immediately applies it to the current ORM API.
|
||||
|
||||
# If ``engine`` is not provided, then ``rattail.engine`` is assumed.
|
||||
# """
|
||||
|
||||
# if engine is None:
|
||||
# engine = rattail.engine
|
||||
|
||||
# if not isinstance(extension, RattailExtension):
|
||||
# extension = get_extension(extension)
|
||||
|
||||
# log.info("Activating extension: %s" % extension.name)
|
||||
# install_extension_schema(extension, engine)
|
||||
|
||||
# session = Session(bind=engine)
|
||||
# if not session.query(ActiveExtension).get(extension.name):
|
||||
# session.add(ActiveExtension(name=extension.name))
|
||||
# session.commit()
|
||||
# session.close()
|
||||
|
||||
# merge_extension_metadata(extension)
|
||||
# extension.extend_classes()
|
||||
# extension.extend_mappers(rattail.metadata)
|
||||
# _active_extensions[extension.name] = extension
|
||||
|
||||
|
||||
# def deactivate_extension(extension, engine=None):
|
||||
# """
|
||||
# Uninstalls an extension's schema from the primary database, and immediately
|
||||
# requests it to restore the ORM API.
|
||||
|
||||
# If ``engine`` is not provided, then ``rattail.engine`` is assumed.
|
||||
# """
|
||||
|
||||
# if engine is None:
|
||||
# engine = rattail.engine
|
||||
|
||||
# if not isinstance(extension, RattailExtension):
|
||||
# extension = get_extension(extension)
|
||||
|
||||
# log.info("Deactivating extension: %s" % extension.name)
|
||||
# if extension.name in _active_extensions:
|
||||
# del _active_extensions[extension.name]
|
||||
|
||||
# session = Session()
|
||||
# ext = session.query(ActiveExtension).get(extension.name)
|
||||
# if ext:
|
||||
# session.delete(ext)
|
||||
# session.commit()
|
||||
# session.close()
|
||||
|
||||
# uninstall_extension_schema(extension, engine)
|
||||
# unmerge_extension_metadata(extension)
|
||||
# extension.restore_classes()
|
||||
|
||||
# clear_mappers()
|
||||
# make_mappers(rattail.metadata)
|
||||
# for name in sorted(_active_extensions, extension_sorter(_active_extensions)):
|
||||
# _active_extensions[name].extend_mappers(rattail.metadata)
|
||||
|
||||
|
||||
def extend_framework():
|
||||
"""
|
||||
Attempts to connect to the primary database and, if successful, inspects it
|
||||
to determine which extensions are active within it. Any such extensions
|
||||
found will be used to extend the ORM/API in-place.
|
||||
"""
|
||||
|
||||
engine = edbob.db.engine
|
||||
|
||||
# Check primary database connection.
|
||||
try:
|
||||
engine.connect()
|
||||
except sqlalchemy.exc.OperationalError:
|
||||
return
|
||||
|
||||
# Check database version to see if core schema is installed.
|
||||
try:
|
||||
db_version = get_database_version(engine)
|
||||
except exceptions.CoreSchemaNotInstalled:
|
||||
return
|
||||
|
||||
# Since extensions may depend on one another, we must first retrieve the
|
||||
# list of active extensions' names from the database and *then* sort them
|
||||
# according to their stated dependencies. (This information is only known
|
||||
# after instantiating the extensions.)
|
||||
|
||||
session = Session()
|
||||
try:
|
||||
active_extensions = session.query(ActiveExtension).all()
|
||||
except sqlalchemy.exc.ProgrammingError:
|
||||
session.close()
|
||||
return
|
||||
|
||||
extensions = {}
|
||||
for ext in active_extensions:
|
||||
extensions[ext.name] = get_extension(ext.name)
|
||||
session.close()
|
||||
|
||||
for name in sorted(extensions, extension_sorter(extensions)):
|
||||
ext = extensions[name]
|
||||
log.info("Applying active extension: %s" % name)
|
||||
merge_extension_metadata(ext)
|
||||
ext.extend_classes()
|
||||
ext.extend_mappers(rattail.metadata)
|
||||
active_extensions[name] = ext
|
||||
|
||||
|
||||
# def extension_active(extension):
|
||||
# """
|
||||
# Returns boolean indicating whether or not the given ``extension`` is active
|
||||
# within the current database.
|
||||
# """
|
||||
|
||||
# if not isinstance(extension, RattailExtension):
|
||||
# extension = get_extension(extension)
|
||||
# return extension.name in _active_extensions
|
||||
|
||||
|
||||
def extension_sorter(extensions):
|
||||
"""
|
||||
Returns a function to be used for sorting extensions according to their
|
||||
inter-dependencies. ``extensions`` should be a dictionary containing the
|
||||
extensions which are to be sorted.
|
||||
"""
|
||||
|
||||
def sorter(name_x, name_y):
|
||||
ext_x = extensions[name_x]
|
||||
ext_y = extensions[name_y]
|
||||
|
||||
if name_y in ext_x.required_extensions:
|
||||
return 1
|
||||
if name_x in ext_y.required_extensions:
|
||||
return -1
|
||||
|
||||
if ext_x.required_extensions and not ext_y.required_extensions:
|
||||
return 1
|
||||
if ext_y.required_extensions and not ext_x.required_extensions:
|
||||
return -1
|
||||
return 0
|
||||
|
||||
return sorter
|
||||
|
||||
|
||||
def get_extension(name):
|
||||
"""
|
||||
Returns a :class:`Extension` instance, according to ``name``. An error is
|
||||
raised if the extension cannot be found.
|
||||
"""
|
||||
|
||||
if name in available_extensions:
|
||||
return available_extensions[name]()
|
||||
raise exceptions.ExtensionNotFound(name)
|
||||
|
||||
|
||||
# def install_extension_schema(extension, engine=None):
|
||||
# """
|
||||
# Installs an extension's schema to the database and adds version control for
|
||||
# it.
|
||||
# """
|
||||
|
||||
# if engine is None:
|
||||
# engine = rattail.engine
|
||||
|
||||
# # Extensionls aren't required to provide metadata...
|
||||
# ext_meta = extension.get_metadata()
|
||||
# if not ext_meta:
|
||||
# return
|
||||
|
||||
# # ...but if they do they must also provide a SQLAlchemy-Migrate repository!
|
||||
# assert extension.schema, "Extension does not implement 'schema': %s" % extension.name
|
||||
|
||||
# meta = rattail.metadata
|
||||
# for table in meta.sorted_tables:
|
||||
# table.tometadata(ext_meta)
|
||||
# for table in ext_meta.sorted_tables:
|
||||
# if table.name not in meta.tables:
|
||||
# table.create(bind=engine, checkfirst=True)
|
||||
|
||||
# migrate.versioning.api.version_control(
|
||||
# str(engine.url), get_repository_path(extension), get_repository_version(extension))
|
||||
|
||||
|
||||
def merge_extension_metadata(ext):
|
||||
"""
|
||||
Merges an extension's metadata with the global ``edbob.db.metadata``
|
||||
instance.
|
||||
|
||||
.. note::
|
||||
``edbob`` uses this internally; you should not need to.
|
||||
"""
|
||||
|
||||
ext_meta = ext.get_metadata()
|
||||
if not ext_meta:
|
||||
return
|
||||
meta = edbob.db.metadata
|
||||
for table in meta.sorted_tables:
|
||||
table.tometadata(ext_meta)
|
||||
for table in ext_meta.sorted_tables:
|
||||
if table.name not in meta.tables:
|
||||
table.tometadata(meta)
|
||||
|
||||
|
||||
# def uninstall_extension_schema(extension, engine=None):
|
||||
# """
|
||||
# Uninstalls an extension's tables from the database represented by
|
||||
# ``engine`` (or ``rattail.engine`` if none is provided), and removes
|
||||
# SQLAlchemy-Migrate version control for the extension.
|
||||
# """
|
||||
|
||||
# if engine is None:
|
||||
# engine = rattail.engine
|
||||
|
||||
# ext_meta = extension.get_metadata()
|
||||
# if not ext_meta:
|
||||
# return
|
||||
|
||||
# schema = ControlledSchema(engine, get_repository_path(extension))
|
||||
# engine.execute(schema.table.delete().where(
|
||||
# schema.table.c.repository_id == schema.repository.id))
|
||||
|
||||
# meta = get_metadata()
|
||||
# for table in meta.sorted_tables:
|
||||
# table.tometadata(ext_meta)
|
||||
# for table in reversed(ext_meta.sorted_tables):
|
||||
# if table.name not in meta.tables:
|
||||
# table.drop(bind=engine)
|
||||
|
||||
|
||||
# def unmerge_extension_metadata(extension):
|
||||
# """
|
||||
# Removes an extension's metadata from the global ``rattail.metadata``
|
||||
# instance.
|
||||
# """
|
||||
|
||||
# ext_meta = extension.get_metadata()
|
||||
# if not ext_meta:
|
||||
# return
|
||||
|
||||
# meta = rattail.metadata
|
||||
# ext_tables = ext_meta.tables.keys()
|
||||
# for table in reversed(meta.sorted_tables):
|
||||
# if table.name in ext_tables:
|
||||
# meta.remove(table)
|
||||
|
||||
|
||||
# # def merge_extension_permissions(extension):
|
||||
# # '''
|
||||
# # Helper function to merge an extension's permission definitions with those of
|
||||
# # the framework. (This should only be called by the framework itself.)
|
||||
# # '''
|
||||
# # from rattail.v1.perms import permissions
|
||||
# # log.debug('Merging permissions from extension: %s' % extension.name)
|
||||
# # for group_name in extension.permissions:
|
||||
# # if group_name not in permissions:
|
||||
# # permissions[group_name] = extension.permissions[group_name]
|
||||
# # elif extension.permissions[group_name][0] != permissions[group_name][0]:
|
||||
# # log.warning("Extension '%s' tries to override UUID of permission group '%s' (but is denied)" % (
|
||||
# # extension.name, group_name))
|
||||
# # else:
|
||||
# # # Extensions may override permission group display names.
|
||||
# # if extension.permissions[group_name][1]:
|
||||
# # permissions[group_name][1] = extension.permissions[group_name][1]
|
||||
# # perms = permissions[group_name][2]
|
||||
# # ext_perms = extension.permissions[group_name][2]
|
||||
# # for perm_name in ext_perms:
|
||||
# # if perm_name not in perms:
|
||||
# # perms[perm_name] = ext_perms[perm_name]
|
||||
# # elif ext_perms[perm_name][0] != perms[perm_name][0]:
|
||||
# # log.warning("Extension '%s' tries to override UUID of permission '%s' (but is denied)" % (
|
||||
# # extension.name, '.'.join((group_name, perm_name))))
|
||||
# # else:
|
||||
# # # Extensions may override permission display names.
|
||||
# # if ext_perms[perm_name][1]:
|
||||
# # perms[perm_name][1] = ext_perms[perm_name][1]
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
from sqlalchemy.orm import mapper, relationship
|
||||
|
||||
import edbob.db.classes as c
|
||||
from edbob.db import classes as c
|
||||
|
||||
|
||||
def make_mappers(metadata):
|
||||
|
@ -60,17 +60,6 @@ def make_mappers(metadata):
|
|||
c.Person, t['people'],
|
||||
properties=dict(
|
||||
|
||||
customers=relationship(
|
||||
c.Customer,
|
||||
backref='person',
|
||||
),
|
||||
|
||||
employee=relationship(
|
||||
c.Employee,
|
||||
back_populates='person',
|
||||
uselist=False,
|
||||
),
|
||||
|
||||
user=relationship(
|
||||
c.User,
|
||||
back_populates='person',
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
from sqlalchemy import *
|
||||
|
||||
from edbob import get_uuid
|
||||
from edbob.sqlalchemy import table_with_uuid
|
||||
|
||||
|
||||
def get_metadata(*args, **kwargs):
|
||||
|
@ -66,9 +66,8 @@ def get_metadata(*args, **kwargs):
|
|||
return None
|
||||
return '%(first_name)s %(last_name)s' % locals()
|
||||
|
||||
people = Table(
|
||||
people = table_with_uuid(
|
||||
'people', metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
Column('first_name', String(50)),
|
||||
Column('last_name', String(50)),
|
||||
Column('display_name', String(100), default=get_person_display_name),
|
||||
|
@ -80,9 +79,8 @@ def get_metadata(*args, **kwargs):
|
|||
Column('permission', String(50), primary_key=True),
|
||||
)
|
||||
|
||||
roles = Table(
|
||||
roles = table_with_uuid(
|
||||
'roles', metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
Column('name', String(25), nullable=False, unique=True),
|
||||
)
|
||||
|
||||
|
@ -92,16 +90,14 @@ def get_metadata(*args, **kwargs):
|
|||
Column('value', Text),
|
||||
)
|
||||
|
||||
users = Table(
|
||||
users = table_with_uuid(
|
||||
'users', metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
Column('username', String(25), nullable=False, unique=True),
|
||||
Column('person_uuid', String(32), ForeignKey('people.uuid')),
|
||||
)
|
||||
|
||||
users_roles = Table(
|
||||
users_roles = table_with_uuid(
|
||||
'users_roles', metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
Column('user_uuid', String(32), ForeignKey('users.uuid')),
|
||||
Column('role_uuid', String(32), ForeignKey('roles.uuid')),
|
||||
)
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.db.perms`` -- Roles & Permissions
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import object_session
|
||||
|
||||
from edbob.db.classes import Role, User, Permission
|
||||
|
||||
|
||||
def get_administrator(session):
|
||||
"""
|
||||
Returns the "Administrator" :class:`rattail.db.classes.Role` instance,
|
||||
attached to the given ``session``.
|
||||
"""
|
||||
|
||||
uuid = 'd937fa8a965611dfa0dd001143047286'
|
||||
admin = session.query(Role).get(uuid)
|
||||
if admin:
|
||||
return admin
|
||||
admin = Role(uuid=uuid, name='Administrator')
|
||||
session.add(admin)
|
||||
return admin
|
||||
|
||||
|
||||
# def has_permission(object_, permission, session=None):
|
||||
# '''
|
||||
# Checks the given ``object_`` (which may be either a :class:`rattail.v1.User` or
|
||||
# a :class:`rattail.v1.Role`) and returns a boolean indicating whether or not the
|
||||
# object is allowed the given permission. ``permission`` may be either a
|
||||
# :class:`rattail.v1.Permission` instance, or the fully-qualified name of one.
|
||||
|
||||
# If ``object_`` is ``None``, the permission check is made against the special
|
||||
# "(Anybody)" role.
|
||||
# '''
|
||||
|
||||
def has_permission(obj, perm):
|
||||
"""
|
||||
Checks the given ``obj`` (which may be either a
|
||||
:class:`rattail.db.classes.User`` or :class:`rattail.db.classes.Role`
|
||||
instance), and returns a boolean indicating whether or not the object is
|
||||
allowed the given permission. ``perm`` should be a fully-qualified
|
||||
permission name, e.g. ``'employees.admin'``.
|
||||
"""
|
||||
|
||||
if isinstance(obj, User):
|
||||
roles = obj.roles
|
||||
elif isinstance(obj, Role):
|
||||
roles = [obj]
|
||||
else:
|
||||
raise TypeError, "You must pass either a User or Role for 'obj'; got: %s" % repr(obj)
|
||||
session = object_session(obj)
|
||||
assert session
|
||||
admin = get_administrator(session)
|
||||
for role in roles:
|
||||
if role is admin:
|
||||
return True
|
||||
for permission in role.permissions:
|
||||
if permission == perm:
|
||||
return True
|
||||
return False
|
4
edbob/db/schema/README
Normal file
4
edbob/db/schema/README
Normal file
|
@ -0,0 +1,4 @@
|
|||
This is a database migration repository.
|
||||
|
||||
More information at
|
||||
http://code.google.com/p/sqlalchemy-migrate/
|
0
edbob/db/schema/__init__.py
Normal file
0
edbob/db/schema/__init__.py
Normal file
5
edbob/db/schema/manage.py
Normal file
5
edbob/db/schema/manage.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from migrate.versioning.shell import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(debug='False')
|
25
edbob/db/schema/migrate.cfg
Normal file
25
edbob/db/schema/migrate.cfg
Normal file
|
@ -0,0 +1,25 @@
|
|||
[db_settings]
|
||||
# Used to identify which repository this database is versioned under.
|
||||
# You can use the name of your project.
|
||||
repository_id=edbob
|
||||
|
||||
# The name of the database table used to track the schema version.
|
||||
# This name shouldn't already be used by your project.
|
||||
# If this is changed once a database is under version control, you'll need to
|
||||
# change the table name in each database too.
|
||||
version_table=migrate_version
|
||||
|
||||
# When committing a change script, Migrate will attempt to generate the
|
||||
# sql for all supported databases; normally, if one of them fails - probably
|
||||
# because you don't have that database installed - it is ignored and the
|
||||
# commit continues, perhaps ending successfully.
|
||||
# Databases in this list MUST compile successfully during a commit, or the
|
||||
# entire commit will fail. List the databases your application will actually
|
||||
# be using to ensure your updates to that database work properly.
|
||||
# This must be a list; example: ['postgres','sqlite']
|
||||
required_dbs=[]
|
||||
|
||||
# When creating new change scripts, Migrate will stamp the new script with
|
||||
# a version number. By default this is latest_version + 1. You can set this
|
||||
# to 'true' to tell Migrate to use the UTC timestamp instead.
|
||||
use_timestamp_numbering=False
|
0
edbob/db/schema/versions/__init__.py
Normal file
0
edbob/db/schema/versions/__init__.py
Normal file
163
edbob/db/util.py
Normal file
163
edbob/db/util.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.db.util`` -- Database Utilities
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
import sqlalchemy.exc
|
||||
import migrate.versioning.api
|
||||
import migrate.exceptions
|
||||
|
||||
# import rattail
|
||||
# from rattail.db import exc as exceptions
|
||||
# from rattail.db import Session
|
||||
# from rattail.db.classes import Role
|
||||
# from rattail.db.model import get_metadata
|
||||
# from rattail.db.perms import get_administrator
|
||||
|
||||
import edbob.db
|
||||
from edbob.db import exceptions
|
||||
from edbob.db.model import get_metadata
|
||||
|
||||
|
||||
# def core_schema_installed(engine=None):
|
||||
# """
|
||||
# Returns boolean indicating whether or not the core schema has been
|
||||
# installed to the database represented by ``engine``. If ``engine`` is not
|
||||
# provided, then ``rattail.engine`` will be assumed.
|
||||
# """
|
||||
|
||||
# if engine is None:
|
||||
# engine = rattail.engine
|
||||
|
||||
# try:
|
||||
# get_database_version(engine)
|
||||
# except exceptions.CoreSchemaNotInstalled:
|
||||
# return False
|
||||
# return True
|
||||
|
||||
|
||||
def get_database_version(engine=None, extension=None):
|
||||
"""
|
||||
Returns a SQLAlchemy-Migrate version number found in the database
|
||||
represented by ``engine``.
|
||||
|
||||
If no engine is provided, :attr:`edbob.db.engine` is assumed.
|
||||
|
||||
If ``extension`` is provided, the version for its schema is returned;
|
||||
otherwise the core schema is assumed.
|
||||
"""
|
||||
|
||||
if engine is None:
|
||||
engine = edbob.db.engine
|
||||
|
||||
try:
|
||||
version = migrate.versioning.api.db_version(
|
||||
str(engine.url), get_repository_path(extension))
|
||||
|
||||
except (sqlalchemy.exc.NoSuchTableError,
|
||||
migrate.exceptions.DatabaseNotControlledError):
|
||||
raise exceptions.CoreSchemaNotInstalled(engine)
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def get_repository_path(extension=None):
|
||||
"""
|
||||
Returns the absolute filesystem path to the SQLAlchemy-Migrate repository
|
||||
for ``extension``.
|
||||
|
||||
If no extension is provided, ``edbob``'s core repository is assumed.
|
||||
"""
|
||||
|
||||
if not extension:
|
||||
from edbob.db import schema
|
||||
return os.path.dirname(schema.__file__)
|
||||
|
||||
return os.path.dirname(extension.schema.__file__)
|
||||
|
||||
|
||||
def get_repository_version(extension=None):
|
||||
"""
|
||||
Returns the version of the SQLAlchemy-Migrate repository for ``extension``.
|
||||
|
||||
If no extension is provided, ``edbob``'s core repository is assumed.
|
||||
"""
|
||||
|
||||
return migrate.versioning.api.version(get_repository_path(extension))
|
||||
|
||||
|
||||
def install_core_schema(engine=None):
|
||||
"""
|
||||
Installs the core schema to the database represented by ``engine``.
|
||||
|
||||
If no engine is provided, :attr:`edbob.db.engine` is assumed.
|
||||
"""
|
||||
|
||||
if not engine:
|
||||
engine = edbob.db.engine
|
||||
|
||||
# Try to connect in order to force an error, if applicable.
|
||||
conn = engine.connect()
|
||||
conn.close()
|
||||
|
||||
# Check DB version to see if core schema is already installed.
|
||||
try:
|
||||
db_version = get_database_version(engine)
|
||||
except exceptions.CoreSchemaNotInstalled:
|
||||
pass
|
||||
else:
|
||||
raise exceptions.CoreSchemaAlreadyInstalled(db_version)
|
||||
|
||||
# Create tables for core schema.
|
||||
metadata = get_metadata()
|
||||
metadata.create_all(bind=engine)
|
||||
|
||||
# Add versioning for core schema.
|
||||
migrate.versioning.api.version_control(
|
||||
str(engine.url), get_repository_path(), get_repository_version())
|
||||
|
||||
# WTF
|
||||
# session = Session(bind=engine)
|
||||
# get_administrator(session)
|
||||
# session.commit()
|
||||
# session.close()
|
||||
|
||||
|
||||
# def upgrade_schema(extension=None, engine=None):
|
||||
# """
|
||||
# Upgrades a schema within the database represented by ``engine`` (or
|
||||
# ``rattail.engine`` if none is provided). If ``extension`` is provided,
|
||||
# then its schema will be upgraded; otherwise the core is assumed.
|
||||
# """
|
||||
|
||||
# if engine is None:
|
||||
# engine = rattail.engine
|
||||
# repo_version = get_repository_version(extension)
|
||||
# db_version = get_database_version(engine, extension)
|
||||
# if db_version < repo_version:
|
||||
# migrate.versioning.api.upgrade(str(engine.url), get_repository_path(extension), repo_version)
|
|
@ -27,13 +27,14 @@
|
|||
"""
|
||||
|
||||
import os
|
||||
# import locale
|
||||
import logging
|
||||
|
||||
from edbob.configuration import AppConfigParser
|
||||
from edbob.configuration import default_system_paths, default_user_paths
|
||||
from edbob.core import graft
|
||||
from edbob.times import set_timezone
|
||||
import edbob
|
||||
from edbob.configuration import (
|
||||
AppConfigParser,
|
||||
default_system_paths,
|
||||
default_user_paths,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ['init']
|
||||
|
@ -83,19 +84,15 @@ def init(appname='edbob', *args, **kwargs):
|
|||
config.read(paths, recurse=not shell)
|
||||
config.configure_logging()
|
||||
|
||||
# loc = config.get('edbob', 'locale')
|
||||
# if loc:
|
||||
# locale.setlocale(locale.LC_ALL, loc)
|
||||
# log.info("Set locale to '%s'" % loc)
|
||||
default_modules = 'edbob.time'
|
||||
modules = config.get('edbob', 'init', default=default_modules)
|
||||
if modules:
|
||||
for name in modules.split(','):
|
||||
name = name.strip()
|
||||
module = __import__(name, globals(), locals(), fromlist=['init'])
|
||||
getattr(module, 'init')(config)
|
||||
# config.inited.append(name)
|
||||
|
||||
tz = config.get('edbob', 'timezone')
|
||||
if tz:
|
||||
set_timezone(tz)
|
||||
log.info("Set timezone to '%s'" % tz)
|
||||
else:
|
||||
log.warning("No timezone configured; falling back to US/Central")
|
||||
set_timezone('US/Central')
|
||||
|
||||
import edbob
|
||||
graft(edbob, locals(), 'config')
|
||||
# config.inited.append('edbob')
|
||||
edbob.graft(edbob, locals(), 'config')
|
||||
edbob.inited = True
|
||||
|
|
|
@ -5,10 +5,15 @@
|
|||
"""
|
||||
|
||||
import os.path
|
||||
import edbob
|
||||
|
||||
import pyramid_beaker
|
||||
from pyramid.config import Configurator
|
||||
|
||||
import edbob
|
||||
|
||||
from {{package}}._version import __version__
|
||||
from {{package}}.db import DBSession
|
||||
|
||||
|
||||
def main(global_config, **settings):
|
||||
"""
|
||||
|
@ -39,7 +44,7 @@ def main(global_config, **settings):
|
|||
config.set_session_factory(session_factory)
|
||||
pyramid_beaker.set_cache_regions_from_settings(settings)
|
||||
|
||||
# Initialize edbob
|
||||
# Configure edbob
|
||||
edbob.basic_logging()
|
||||
edbob.init('{{package}}', os.path.abspath(settings['edbob.config']))
|
||||
|
||||
|
|
82
edbob/pyramid/scaffolds/edbob/+package+/commands.py_tmpl
Normal file
82
edbob/pyramid/scaffolds/edbob/+package+/commands.py_tmpl
Normal file
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
``{{package}}.commands`` -- Console Commands
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import edbob
|
||||
from edbob import commands
|
||||
|
||||
from {{package}} import __version__
|
||||
|
||||
|
||||
class Command(commands.Command):
|
||||
"""
|
||||
The primary command for {{project}}.
|
||||
"""
|
||||
|
||||
name = '{{package}}'
|
||||
version = __version__
|
||||
description = "{{project}}"
|
||||
long_description = ''
|
||||
|
||||
|
||||
class InitDatabaseCommand(commands.Subcommand):
|
||||
"""
|
||||
Initializes the database. This is meant to be leveraged as part of setting
|
||||
up the application. The database used by this command will be determined
|
||||
by config, for example::
|
||||
|
||||
.. highlight:: ini
|
||||
|
||||
[edbob.db]
|
||||
sqlalchemy.url = postgresql://user:pass@localhost/{{package}}
|
||||
"""
|
||||
|
||||
name = 'init-db'
|
||||
description = "Initialize the database"
|
||||
|
||||
def run(self, args):
|
||||
from edbob.db import engine, Session
|
||||
from edbob.db.util import install_core_schema
|
||||
from edbob.db.exceptions import CoreSchemaAlreadyInstalled
|
||||
|
||||
# Install core schema to database.
|
||||
try:
|
||||
install_core_schema(engine)
|
||||
except CoreSchemaAlreadyInstalled, err:
|
||||
print err
|
||||
return
|
||||
|
||||
from edbob.db.classes import Role, User
|
||||
from edbob.db.auth import administrator_role
|
||||
|
||||
session = Session()
|
||||
|
||||
# Create 'admin' user with full rights.
|
||||
admin = User(username='admin', password='admin')
|
||||
admin.roles.append(administrator_role(session))
|
||||
session.add(admin)
|
||||
|
||||
# Do any other bootstrapping you like here...
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
print "Initialized database %s" % engine.url
|
||||
|
||||
|
||||
def main(*args):
|
||||
"""
|
||||
The primary entry point for the command system.
|
||||
"""
|
||||
|
||||
if args:
|
||||
args = list(args)
|
||||
else:
|
||||
args = sys.argv[1:]
|
||||
|
||||
cmd = Command()
|
||||
cmd.run(*args)
|
11
edbob/pyramid/scaffolds/edbob/+package+/db.py_tmpl
Normal file
11
edbob/pyramid/scaffolds/edbob/+package+/db.py_tmpl
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
``{{package}}.db`` -- Database Stuff
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
from zope.sqlalchemy import ZopeTransactionExtension
|
||||
|
||||
|
||||
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
|
|
@ -5,9 +5,13 @@
|
|||
Welcome to the {{project}} project.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Install the project with::
|
||||
- cd <directory containing this file>
|
||||
|
||||
$ pip install {{package}}
|
||||
- $venv/bin/python setup.py develop
|
||||
|
||||
- $venv/bin/populate_{{package}} development.ini
|
||||
|
||||
- $venv/bin/pserve --reload development.ini
|
||||
|
|
|
@ -51,6 +51,9 @@ port = 6543
|
|||
[edbob]
|
||||
include_config = ['%(here)s/production.ini']
|
||||
|
||||
[edbob.db]
|
||||
sqlalchemy.url = sqlite:///{{package}}.sqlite
|
||||
|
||||
|
||||
####################
|
||||
# logging
|
||||
|
|
|
@ -41,9 +41,12 @@ port = 6543
|
|||
####################
|
||||
|
||||
[edbob]
|
||||
timezone = US/Central
|
||||
init = edbob.time, edbob.db
|
||||
# shell.python = ipython
|
||||
|
||||
[edbob.db]
|
||||
sqlalchemy.url = postgresql://user:pass@localhost/{{package}}
|
||||
|
||||
[edbob.mail]
|
||||
smtp.server = localhost
|
||||
# smtp.username = user
|
||||
|
@ -56,6 +59,9 @@ recipients.default = [
|
|||
]
|
||||
subject.default = Message from {{project}}
|
||||
|
||||
[edbob.time]
|
||||
timezone = US/Central
|
||||
|
||||
|
||||
####################
|
||||
# logging
|
||||
|
|
|
@ -39,12 +39,19 @@ requires = [
|
|||
#
|
||||
# package # low high
|
||||
|
||||
'decorator', # 3.3.2
|
||||
'edbob', # 0.1a1
|
||||
'Mako', # 0.6.2
|
||||
'pyramid', # 1.3b2
|
||||
'pyramid_beaker', # 0.6.1
|
||||
'pyramid_debugtoolbar', # 1.0
|
||||
'pyramid_tm', # 0.3
|
||||
'SQLAlchemy', # 0.7.6
|
||||
'Tempita', # 0.5.1
|
||||
'transaction', # 1.2.0
|
||||
'waitress', # 0.8.1
|
||||
'WebHelpers', # 1.3
|
||||
'zope.sqlalchemy', # 0.7
|
||||
]
|
||||
|
||||
|
||||
|
@ -77,6 +84,12 @@ setup(
|
|||
zip_safe = False,
|
||||
entry_points = """
|
||||
|
||||
[console_scripts]
|
||||
{{package}} = {{package}}.commands:main
|
||||
|
||||
[{{package}}.commands]
|
||||
init-db = {{package}}.commands:InitDatabaseCommand
|
||||
|
||||
[paste.app_factory]
|
||||
main = {{package}}:main
|
||||
|
||||
|
|
|
@ -26,21 +26,56 @@
|
|||
``edbob.pyramid.subscribers`` -- Subscribers
|
||||
"""
|
||||
|
||||
from pyramid.security import authenticated_userid
|
||||
# from sqlahelper import get_session
|
||||
|
||||
import edbob
|
||||
from edbob.db.auth import has_permission
|
||||
from edbob.pyramid import helpers
|
||||
|
||||
|
||||
def add_renderer_globals(event):
|
||||
def before_render(event):
|
||||
"""
|
||||
Adds goodies to the global template renderer context.
|
||||
Adds goodies to the global template renderer context:
|
||||
|
||||
* ``h``
|
||||
* ``url``
|
||||
* ``edbob``
|
||||
"""
|
||||
|
||||
renderer_globals = event
|
||||
renderer_globals['h'] = helpers
|
||||
renderer_globals['edbob'] = edbob
|
||||
renderer_globals['url'] = event['request'].route_url
|
||||
renderer_globals['edbob'] = edbob
|
||||
|
||||
|
||||
def context_found(event):
|
||||
"""
|
||||
This hook attaches the :class:`edbob.User` instance for the currently
|
||||
logged-in user to the request (if there is one) as ``request.user``.
|
||||
|
||||
Also adds a ``has_perm()`` function to the request, which is a shortcut for
|
||||
:func:`edbob.db.auth.has_permission()`.
|
||||
"""
|
||||
|
||||
def has_perm_func(request):
|
||||
def has_perm(perm):
|
||||
if not request.current_user:
|
||||
return False
|
||||
return has_permission(request.current_user, perm)
|
||||
return has_perm
|
||||
|
||||
request = event.request
|
||||
request.user = None
|
||||
request.has_perm = has_perm_func(request)
|
||||
|
||||
uuid = authenticated_userid(request)
|
||||
if uuid:
|
||||
request.user = get_session().query(rattail.User).get(uuid)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.add_subscriber('edbob.pyramid.subscribers:add_renderer_globals',
|
||||
config.add_subscriber('edbob.pyramid.subscribers:before_render',
|
||||
'pyramid.events.BeforeRender')
|
||||
config.add_subscriber('edbob.pyramid.subscribers.context_found',
|
||||
'pyramid.events.ContextFound')
|
||||
|
|
|
@ -31,6 +31,9 @@ import os.path
|
|||
|
||||
from pyramid.response import Response
|
||||
from pyramid.view import view_config
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from edbob.db.auth import authenticate_user
|
||||
|
||||
|
||||
_here = os.path.join(os.path.dirname(__file__), os.pardir)
|
||||
|
@ -51,6 +54,29 @@ def home(context, request):
|
|||
|
||||
@view_config(route_name='login', renderer='login.mako')
|
||||
def login(context, request):
|
||||
"""
|
||||
The login view, responsible for displaying and handling the login form.
|
||||
"""
|
||||
|
||||
if request.params.get('referer'):
|
||||
referer = request.params['referer']
|
||||
elif request.session.get('referer'):
|
||||
referer = request.session.pop('referer')
|
||||
else:
|
||||
referer = request.referer or request.route_url('home')
|
||||
# if request.current_user:
|
||||
# return HTTPFound(location=referer)
|
||||
# form = Form(self.request, schema=UserLogin)
|
||||
# if form.validate():
|
||||
# user = authenticate_user(self.Session(), form.data['username'], form.data['password'])
|
||||
# if user:
|
||||
# self.request.session.flash("%s logged in at %s" % (
|
||||
# user.display_name,
|
||||
# datetime.datetime.now().strftime("%I:%M %p")))
|
||||
# headers = remember(self.request, user.uuid)
|
||||
# return HTTPFound(location=referer, headers=headers)
|
||||
# self.request.session.flash("Invalid username or password.")
|
||||
# return {'form':FormRenderer(form), 'referer':referer}
|
||||
return {}
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,15 @@
|
|||
``edbob.sqlalchemy`` -- SQLAlchemy Stuff
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from sqlalchemy import Table, Column, String
|
||||
|
||||
from edbob.core import get_uuid
|
||||
|
||||
|
||||
__all__ = ['getset_factory', 'table_with_uuid']
|
||||
|
||||
|
||||
def getset_factory(collection_class, proxy):
|
||||
"""
|
||||
|
@ -37,3 +46,29 @@ def getset_factory(collection_class, proxy):
|
|||
return getattr(obj, proxy.value_attr)
|
||||
setter = lambda obj, val: setattr(obj, proxy.value_attr, val)
|
||||
return getter, setter
|
||||
|
||||
|
||||
def table_with_uuid(name, metadata, *args, **kwargs):
|
||||
"""
|
||||
Convenience function to abstract the addition of the ``uuid`` column to a
|
||||
new table. Can be used to replace this::
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
Table(
|
||||
'things', metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
Column('name', String(50)),
|
||||
)
|
||||
|
||||
...with this::
|
||||
|
||||
table_with_uuid(
|
||||
'things', metadata,
|
||||
Column('name', String(50)),
|
||||
)
|
||||
"""
|
||||
|
||||
return Table(name, metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
*args, **kwargs)
|
||||
|
|
|
@ -23,17 +23,35 @@
|
|||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.times`` -- Date & Time Utilities
|
||||
``edbob.time`` -- Date & Time Utilities
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
|
||||
|
||||
__all__ = ['local_time', 'set_timezone', 'utc_time']
|
||||
|
||||
_timezone = None
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
timezone = None
|
||||
|
||||
|
||||
def init(config):
|
||||
"""
|
||||
Initializes the time framework. Currently this only sets the local
|
||||
timezone according to config.
|
||||
"""
|
||||
|
||||
tz = config.get('edbob.time', 'timezone')
|
||||
if tz:
|
||||
set_timezone(tz)
|
||||
log.info("Set timezone to '%s'" % tz)
|
||||
else:
|
||||
log.warning("No timezone configured; falling back to US/Central")
|
||||
set_timezone('US/Central')
|
||||
|
||||
|
||||
def local_time(timestamp=None):
|
||||
"""
|
||||
|
@ -51,11 +69,11 @@ def local_time(timestamp=None):
|
|||
should none be specified. ``timestamp`` will be returned unchanged.
|
||||
"""
|
||||
|
||||
if _timezone:
|
||||
if timezone:
|
||||
if timestamp is None:
|
||||
timestamp = datetime.datetime.utcnow()
|
||||
timestamp = pytz.utc.localize(timestamp)
|
||||
return timestamp.astimezone(_timezone)
|
||||
return timestamp.astimezone(timezone)
|
||||
|
||||
if timestamp is None:
|
||||
timestamp = datetime.datetime.now()
|
||||
|
@ -67,21 +85,25 @@ def set_timezone(tz):
|
|||
Sets edbob's notion of the "local" timezone. ``tz`` should be an Olson
|
||||
name.
|
||||
|
||||
.. highlight:: ini
|
||||
|
||||
You usually don't need to call this yourself, since it's called by
|
||||
:func:`edbob.init()` whenever ``edbob.conf`` includes a timezone::
|
||||
:func:`edbob.init()` whenever the config file includes a timezone (but
|
||||
only as long as ``edbob.time`` is configured to be initialized)::
|
||||
|
||||
.. highlight:: ini
|
||||
|
||||
[edbob]
|
||||
init = ['edbob.time']
|
||||
|
||||
[edbob.time]
|
||||
timezone = US/Central
|
||||
"""
|
||||
|
||||
global _timezone
|
||||
global timezone
|
||||
|
||||
if tz is None:
|
||||
_timezone = None
|
||||
timezone = None
|
||||
else:
|
||||
_timezone = pytz.timezone(tz)
|
||||
timezone = pytz.timezone(tz)
|
||||
|
||||
|
||||
def utc_time(timestamp=None):
|
19
setup.py
19
setup.py
|
@ -99,15 +99,18 @@ setup(
|
|||
|
||||
extras_require = {
|
||||
#
|
||||
# Same guidelines apply to the extra dependencies:
|
||||
# Same guidelines apply to the extra dependency versions.
|
||||
|
||||
# 'db': [
|
||||
# #
|
||||
# # package # low high
|
||||
# #
|
||||
# 'SQLAlchemy', # 0.6.7
|
||||
# 'sqlalchemy-migrate', # 0.6.1
|
||||
# ],
|
||||
'db': [
|
||||
#
|
||||
# package # low high
|
||||
#
|
||||
'decorator', # 3.3.2
|
||||
'py-bcrypt', # 0.2
|
||||
'SQLAlchemy', # 0.7.6
|
||||
'sqlalchemy-migrate', # 0.7.2
|
||||
'Tempita', # 0.5.1
|
||||
],
|
||||
|
||||
'docs': [
|
||||
#
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue