diff --git a/docs/index.txt b/docs/index.txt index 1e5c5f9..6e2683a 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -53,7 +53,7 @@ Custom view, factory, forms In the toward examples we just pass a ``package`` parameter to :func:`~pyramid_formalchemy.configure`. By default the function will try to -load ``package.models`` ``package.models.DBSession`` and ``package.forms`` but +load ``package.models``, ``package.models.DBSession`` and ``package.forms`` but you can override this. In this case you don't need to specify a package:: @@ -72,6 +72,32 @@ Having fun with the query_factory parameter .. literalinclude:: ../../formalchemy_project/__init__.py :pyobject: main +Custom views per model +---------------------- + +You can also register custom CRUD views per ModelListing:: + + + config.formalchemy_model_view('admin', + model='pyramidapp.models.Foo', + context='pyramid_formalchemy.resources.ModelListing', + renderer='templates/foolisting.pt', + attr='listing', + request_method='GET', + permission='view') + +and per Model:: + + + config.formalchemy_model_view('admin', + model='pyramidapp.models.Foo', + context='pyramid_formalchemy.resources.Model', + name='', + renderer='templates/fooshow.pt', + attr='show', + request_method='GET', + permission='view') + Setting permissions =================== diff --git a/pyramid_formalchemy/__init__.py b/pyramid_formalchemy/__init__.py index 37c76a1..58efe68 100644 --- a/pyramid_formalchemy/__init__.py +++ b/pyramid_formalchemy/__init__.py @@ -7,6 +7,30 @@ def includeme(config): config.add_static_view('fa_admin', 'pyramid_formalchemy:static') config.add_directive('formalchemy_admin', 'pyramid_formalchemy.formalchemy_admin') config.add_directive('formalchemy_model', 'pyramid_formalchemy.formalchemy_model') + config.add_directive('formalchemy_model_view', 'pyramid_formalchemy.formalchemy_model_view') + config.registry.pyramid_formalchemy_views = {} + +def formalchemy_model_view(config, route_name, + model=None, + name='', + view='pyramid_formalchemy.views.ModelView', + context='pyramid_formalchemy.resources.Model', **kwargs): + """custom model view registration""" + + model = config.maybe_dotted(model) + context = config.maybe_dotted(context) + mixin_name = '%sCustom%s_%s_%s' % (model.__name__, context.__name__, + route_name, kwargs.get('request_method','GET')) + + factory = type(mixin_name, (context,), {}) + config.registry.pyramid_formalchemy_views[factory.__name__] = factory + + kw = dict(route_name=route_name, view=view) + kw.update(kwargs) + + config.add_view(context=factory, + name=name, + **kw) def formalchemy_model(config, route_name, factory='pyramid_formalchemy.resources.ModelListing', diff --git a/pyramid_formalchemy/resources.py b/pyramid_formalchemy/resources.py index 73bcd8f..a82a8ce 100644 --- a/pyramid_formalchemy/resources.py +++ b/pyramid_formalchemy/resources.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from pyramid.exceptions import NotFound +from sqlalchemy import exceptions as sqlalchemy_exceptions import logging log = logging.getLogger(__name__) @@ -74,7 +75,14 @@ class Models(Base): if item in ('json', 'xhr'): self.request.format = item return self - model = ModelListing(self.request, item) + + self.request.model_name = item + model_class = self.get_model() + mixin_name = '%sCustom%s_%s_%s' % (model_class.__name__, ModelListing.__name__, + self.request.route_name, self.request.method) + mixin = type(mixin_name, (ModelListing, ), {}) + factory = self.request.registry.pyramid_formalchemy_views.get(mixin.__name__, mixin) + model = factory(self.request, item) model.__parent__ = self if hasattr(model, '__acl__'): # propagate permissions to parent @@ -102,9 +110,15 @@ class ModelListing(Base): if item in ('json', 'xhr'): self.request.format = item return self - if item in ('new',): + + mixin_name = '%sCustom%s_%s_%s' % (self.request.model_class.__name__, Model.__name__, + self.request.route_name, self.request.method) + mixin = type(str(mixin_name), (Model, ), {}) + factory = self.request.registry.pyramid_formalchemy_views.get(mixin.__name__, mixin) + try: + model = factory(self.request, item) + except NotFound: raise KeyError() - model = Model(self.request, item) model.__parent__ = self return model @@ -116,7 +130,13 @@ class Model(Base): def __init__(self, request, name): Base.__init__(self, request, name) query = request.session_factory.query(request.model_class) - request.model_instance = request.query_factory(request, query, id=name) + try: + request.model_instance = request.query_factory(request, query, id=name) + except sqlalchemy_exceptions.SQLAlchemyError, exc: + log.exception(exc) + request.session_factory().rollback() + raise NotFound(request.path) + if request.model_instance is None: raise NotFound(request.path) request.model_id = name diff --git a/pyramid_formalchemy/views.py b/pyramid_formalchemy/views.py index 9931307..4be9245 100644 --- a/pyramid_formalchemy/views.py +++ b/pyramid_formalchemy/views.py @@ -8,6 +8,7 @@ from formalchemy.fields import Field from formalchemy import fatypes from pyramid.renderers import get_renderer from pyramid.response import Response +from pyramid.security import has_permission from pyramid import httpexceptions as exc from pyramid.exceptions import NotFound from pyramid_formalchemy.utils import TemplateEngine @@ -55,15 +56,17 @@ class ModelView(object): models = {} if isinstance(request.models, list): for model in request.models: - key = model.__name__ - models[key] = request.fa_url(key, request.format) + if has_permission('view', model, request): + key = model.__name__ + models[key] = request.fa_url(key, request.format) else: for key, obj in request.models.__dict__.iteritems(): if not key.startswith('_'): if Document is not None: try: if issubclass(obj, Document): - models[key] = request.fa_url(key, request.format) + if has_permission('view', obj, request): + models[key] = request.fa_url(key, request.format) continue except: pass @@ -73,7 +76,8 @@ class ModelView(object): continue if not isinstance(obj, type): continue - models[key] = request.fa_url(key, request.format) + if has_permission('view', obj, request): + models[key] = request.fa_url(key, request.format) if kwargs.get('json'): return models return self.render(models=models) @@ -123,7 +127,7 @@ class ModelView(object): def render_json_format(self, fs=None, **kwargs): request = self.request request.override_renderer = 'json' - if fs: + if fs is not None: try: fields = fs.jsonify() except AttributeError: diff --git a/pyramidapp/pyramidapp/__init__.py b/pyramidapp/pyramidapp/__init__.py index e73ae23..147bc6f 100644 --- a/pyramidapp/pyramidapp/__init__.py +++ b/pyramidapp/pyramidapp/__init__.py @@ -22,6 +22,25 @@ def main(global_config, **settings): # register an admin UI for a single model config.formalchemy_model('foo', package='pyramidapp', model='pyramidapp.models.Foo') + # register custom model listing + config.formalchemy_model_view('admin', + model='pyramidapp.models.Foo', + context='pyramid_formalchemy.resources.ModelListing', + renderer='templates/foolisting.pt', + attr='listing', + request_method='GET', + permission='view') + + # register custom model view + config.formalchemy_model_view('admin', + model='pyramidapp.models.Foo', + context='pyramid_formalchemy.resources.Model', + name='', + renderer='templates/fooshow.pt', + attr='show', + request_method='GET', + permission='view') + return config.make_wsgi_app() diff --git a/pyramidapp/pyramidapp/models.py b/pyramidapp/pyramidapp/models.py index dc981d8..b9a145d 100644 --- a/pyramidapp/pyramidapp/models.py +++ b/pyramidapp/pyramidapp/models.py @@ -14,7 +14,7 @@ from sqlalchemy.orm import scoped_session from sqlalchemy.orm import sessionmaker from zope.sqlalchemy import ZopeTransactionExtension -from pyramid.security import Allow, ALL_PERMISSIONS +from pyramid.security import Allow, Authenticated, ALL_PERMISSIONS DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() @@ -27,6 +27,12 @@ class MyModel(Base): class Foo(Base): __tablename__ = 'foo' + __acl__ = [ + (Allow, 'admin', ALL_PERMISSIONS), + (Allow, Authenticated, 'view'), + (Allow, 'editor', 'edit'), + (Allow, 'manager', ('new', 'edit', 'delete')), + ] id = Column(Integer, primary_key=True) bar = Column(Unicode(255)) diff --git a/pyramidapp/pyramidapp/templates/foolisting.pt b/pyramidapp/pyramidapp/templates/foolisting.pt new file mode 100644 index 0000000..f3feb77 --- /dev/null +++ b/pyramidapp/pyramidapp/templates/foolisting.pt @@ -0,0 +1,16 @@ + +
++ + + + + + ${F_('Back')} + +
+