Source code for flask_diced

# -*- coding: utf-8 -*-
"""Flask-Diced - CRUD views generator for Flask"""

from flask import flash, redirect, render_template, url_for


__version__ = '0.4.dev0'

__all__ = [
    'Detail', 'Index', 'Create', 'Edit', 'Delete',
    'Base', 'Diced',
    'persistence_methods',
]


def apply_decorators(func, decorators):
    for decorator in reversed(decorators):
        func = decorator(func)
    return func


[docs]def persistence_methods(datastore): """class decorator that adds persistence methods to the model class :param datastore: SQLAlchemy style datastore, should sopport :code:`datastore.session.add()`, :code:`datastore.session.delete()` and :code:`datastore.session.commit()` for model persistence. Two persistence methods will be added to the decorated class :data:`save(self, commit=True)` the save method :data:`delete(self, commit=True)` the delete method """ def class_decorator(cls): def save(self, commit=True): datastore.session.add(self) if commit: datastore.session.commit() def delete(self, commit=True): datastore.session.delete(self) if commit: datastore.session.commit() cls.save = save cls.delete = delete return cls return class_decorator
[docs]class Detail(object): """detail view mixin""" #: decorators to be applied to detail view detail_decorators = () #: the endpoint for the detail view URL rule detail_endpoint = 'detail' #: the URL rule for the detail view detail_rule = '/<int:pk>/' @property def detail_template(self): """default template name for detail view generated with :attr:`~Base.object_name` and :attr:`detail_endpoint` """ return '{}/{}.html'.format(self.object_name, self.detail_endpoint)
[docs] def detail_view(self, pk): """detail view function :param pk: the primary key of the model to be shown. """ context = self.detail_view_context( {self.object_name: self.query_object(pk)}) return render_template(self.detail_template, **context)
[docs] def detail_view_context(self, context): """detail view context :param context: the context that will be provided to detail view, can be modified as needed. :return: the context to be used for detail view """ return context
[docs] def register_detail_view(self, blueprint): """register detail view to blueprint :param blueprint: the Flask Blueprint or Application object to which the detail view will be registered. """ view = apply_decorators(self.detail_view, self.detail_decorators) blueprint.add_url_rule(self.detail_rule, self.detail_endpoint, view)
[docs]class Index(object): """index view mixin""" #: decorators to be applied to index view index_decorators = () #: the endpoint for the index view URL rule index_endpoint = 'index' #: the URL rule for the index view index_rule = '/' @property def index_template(self): """default template name for index view generated with :attr:`~Base.object_name` and :attr:`index_endpoint` """ return '{}/{}.html'.format(self.object_name, self.index_endpoint)
[docs] def index_view(self): """index view function""" context = self.index_view_context( {self.object_list_name: self.query_all()}) return render_template(self.index_template, **context)
[docs] def index_view_context(self, context): """index view context :param context: the context that will be provided to index view, can be modified as needed. :return: the context to be used for index view """ return context
[docs] def register_index_view(self, blueprint): """register index view to blueprint :param blueprint: the Flask Blueprint or Application object to which the index view will be registered. """ view = apply_decorators(self.index_view, self.index_decorators) blueprint.add_url_rule(self.index_rule, self.index_endpoint, view)
[docs]class Create(object): """create view mixin""" #: decorators to be applied to create view create_decorators = () #: the endpoint for the create view URL rule create_endpoint = 'create' #: the message to be flashed for the next request when done create_flash_message = None #: the form class for new object, with Flask-WFT compatible API, #: this attribute is **mandatory** if create view is enabled unless the #: default view :meth:`create_view` is overridden and does not use it create_form_class = None #: the name for variable representing the form in template create_form_name = 'form' #: the name of view to redirect the client to when done create_redirect_to_view = '.index' #: the URL rule for the create view create_rule = '/create/' @property def create_redirect_url(self): """the url the client will be redirected to when done the default value is the url of :attr:`create_redirect_to_view` """ return url_for(self.create_redirect_to_view) @property def create_template(self): """default template name for create view generated with :attr:`~Base.object_name` and :attr:`create_endpoint` """ return '{}/{}.html'.format(self.object_name, self.create_endpoint)
[docs] def create_view(self): """create view function""" form = self.create_form_class() if form.validate_on_submit(): obj = self.model() form.populate_obj(obj) obj.save() message = self.create_flash_message if message is None: message = self.object_name + ' created' if message: flash(message) return redirect(self.create_redirect_url) context = self.create_view_context({self.create_form_name: form}) return render_template(self.create_template, **context)
[docs] def create_view_context(self, context): """create view context :param context: the context that will be provided to create view, can be modified as needed. :return: the context to be used for create view """ return context
[docs] def register_create_view(self, blueprint): """register create view to blueprint :param blueprint: the Flask Blueprint or Application object to which the create view will be registered. """ view = apply_decorators(self.create_view, self.create_decorators) blueprint.add_url_rule( self.create_rule, self.create_endpoint, view, methods=['GET', 'POST'])
[docs]class Edit(object): """edit view mixin""" #: decorators to be applied to edit view edit_decorators = () #: the endpoint for the edit view URL rule edit_endpoint = 'edit' #: the message to be flashed for the next request when done edit_flash_message = None #: the form class for editing object, with Flask-WFT compatible API, #: this attribute is **mandatory** if edit view is enabled unless the #: default view :meth:`edit_view` is overridden and does not use it edit_form_class = None #: the name for variable representing the form in template. edit_form_name = 'form' #: the name of view to redirect the client to when done edit_redirect_to_view = '.index' #: the URL rule for the edit view edit_rule = '/<int:pk>/edit/' @property def edit_redirect_url(self): """the url the client will be redirected to when done the default value is the url of :attr:`edit_redirect_to_view` """ return url_for(self.edit_redirect_to_view) @property def edit_template(self): """default template name for edit view generated with :attr:`~Base.object_name` and :attr:`edit_endpoint` """ return '{}/{}.html'.format(self.object_name, self.edit_endpoint)
[docs] def edit_view(self, pk): """edit view function :param pk: the primary key of the model to be edited. """ obj = self.query_object(pk) form = self.edit_form_class(obj=obj) if form.validate_on_submit(): form.populate_obj(obj) obj.save() message = self.edit_flash_message if message is None: message = self.object_name + ' updated' if message: flash(message) return redirect(self.edit_redirect_url) context = self.edit_view_context({self.edit_form_name: form}) return render_template(self.edit_template, **context)
[docs] def edit_view_context(self, context): """edit view context :param context: the context that will be provided to edit view, can be modified as needed. :return: the context to be used for edit view """ return context
[docs] def register_edit_view(self, blueprint): """register edit view to blueprint :param blueprint: the Flask Blueprint or Application object to which the edit view will be registered. """ view = apply_decorators(self.edit_view, self.edit_decorators) blueprint.add_url_rule( self.edit_rule, self.edit_endpoint, view, methods=['GET', 'POST'])
[docs]class Delete(object): """delete view mixin""" #: decorators to be applied to delete view delete_decorators = () #: the endpoint for the delete view URL rule delete_endpoint = 'delete' #: the message to be flashed for the next request when done delete_flash_message = None #: the form class for deletion confirmation, should validate if confirmed #: this attribute is **mandatory** if delete view is enabled unless the #: default view :meth:`delete_view` is overridden and does not use it delete_form_class = None #: the name for variable representing the form in template delete_form_name = 'form' #: the name of view to redirect the client to when done delete_redirect_to_view = '.index' #: the URL rule for the delete view delete_rule = '/<int:pk>/delete/' @property def delete_redirect_url(self): """the url the client will be redirected to when done the default value is the url of :attr:`delete_redirect_to_view` """ return url_for(self.delete_redirect_to_view) @property def delete_template(self): """default template name for delete view generated with :attr:`~Base.object_name` and :attr:`delete_endpoint` """ return '{}/{}.html'.format(self.object_name, self.delete_endpoint)
[docs] def delete_view(self, pk): """delete view function :param pk: the primary key of the model to be deleted. """ obj = self.query_object(pk) form = self.delete_form_class(obj=obj) if form.validate_on_submit(): obj.delete() message = self.delete_flash_message if message is None: message = self.object_name + ' deleted' if message: flash(message) return redirect(self.delete_redirect_url) context = self.delete_view_context({self.delete_form_name: form}) return render_template(self.delete_template, **context)
[docs] def delete_view_context(self, context): """delete view context :param context: the context that will be provided to delete view, can be modified as needed. :return: the context to be used for delete view """ return context
[docs] def register_delete_view(self, blueprint): """register delete view to blueprint :param blueprint: the Flask Blueprint or Application object to which the delete view will be registered. """ view = apply_decorators(self.delete_view, self.delete_decorators) blueprint.add_url_rule( self.delete_rule, self.delete_endpoint, view, methods=['GET', 'POST'])
[docs]class Base(object): """base class with properties and methods used by mixins""" #: views that will be registered when :meth:`register` is called views = {'detail', 'index', 'create', 'edit', 'delete'} #: views that will not be registered when :meth:`register` is called, even #: if they are also listed in :attr:`views` exclude_views = set() #: the model class, this attribute is **mandatory** model = None @property def object_list_name(self): """default name for variable representing list of objects in templates generated with :attr:`object_name` in detault implementation. """ return self.object_name + '_list' @property def object_name(self): """default name for variable representing object in templates generated with the name of model class in detault implementation. """ return getattr(self.model, '__name__', 'object').lower()
[docs] def __init__(self, **options): """create an instance of view generator all keyword arguments passed in will be set as the instance's attribute if the name is not starting with '_' """ self.__dict__.update( (k, v) for (k, v) in options.items() if not k.startswith('__'))
[docs] def query_object(self, pk): """returns the object with matching :code:`pk`""" return self.model.query.get_or_404(pk)
[docs] def query_all(self): """returns all objects""" return self.model.query.all()
[docs] def register(self, blueprint): """register all enabled views to the :code:`blueprint` :param blueprint: the Flask Blueprint or Application object to which enalbed views will be registered. """ for name in set(self.views) - set(self.exclude_views): getattr(self, '_'.join(['register', name, 'view']))(blueprint)
[docs]class Diced(Detail, Index, Create, Edit, Delete, Base): """CRUD views generator"""