~homeworkprod/byceps

68865d4ac1ac42da7b4495a9c0141ba7d56fe637 — Jochen Kupperschmidt 4 years ago 9927a57
Split template decorator off into a new framework module
36 files changed, 112 insertions(+), 100 deletions(-)

M byceps/blueprints/admin_dashboard/views.py
M byceps/blueprints/authentication/views.py
M byceps/blueprints/authorization_admin/views.py
M byceps/blueprints/board/views.py
M byceps/blueprints/board_admin/views.py
M byceps/blueprints/brand_admin/views.py
M byceps/blueprints/news/views.py
M byceps/blueprints/news_admin/views.py
M byceps/blueprints/newsletter_admin/views.py
M byceps/blueprints/orga_admin/views.py
M byceps/blueprints/orga_presence/views.py
M byceps/blueprints/orga_team/views.py
M byceps/blueprints/orga_team_admin/views.py
M byceps/blueprints/party/views.py
M byceps/blueprints/party_admin/views.py
M byceps/blueprints/seating/views.py
M byceps/blueprints/seating_admin/views.py
M byceps/blueprints/shop/orders/views.py
M byceps/blueprints/shop_article_admin/views.py
M byceps/blueprints/shop_order/views.py
M byceps/blueprints/shop_order_admin/views.py
M byceps/blueprints/snippet_admin/views.py
M byceps/blueprints/style_guide/views.py
M byceps/blueprints/terms/views.py
M byceps/blueprints/terms_admin/views.py
M byceps/blueprints/ticketing/views.py
M byceps/blueprints/ticketing_admin/views.py
M byceps/blueprints/tourney/views.py
M byceps/blueprints/tourney_admin/views.py
M byceps/blueprints/user/views.py
M byceps/blueprints/user_admin/views.py
M byceps/blueprints/user_avatar/views.py
M byceps/blueprints/user_badge/views.py
M byceps/blueprints/user_group/views.py
A byceps/util/framework/templating.py
M byceps/util/templating.py
M byceps/blueprints/admin_dashboard/views.py => byceps/blueprints/admin_dashboard/views.py +1 -1
@@ 29,7 29,7 @@ from ...services.terms import service as terms_service
from ...services.ticketing import ticket_service
from ...services.user import service as user_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated

from ..authorization.decorators import permission_required
from ..authorization.registry import permission_registry

M byceps/blueprints/authentication/views.py => byceps/blueprints/authentication/views.py +1 -1
@@ 22,7 22,7 @@ from ...services.user_avatar import service as user_avatar_service
from ...services.verification_token import service as verification_token_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_notice, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.registry import permission_registry

M byceps/blueprints/authorization_admin/views.py => byceps/blueprints/authorization_admin/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort
from ...services.authorization import service as authorization_service
from ...services.user import service as user_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated

from ..authorization.decorators import permission_required
from ..authorization.registry import permission_registry

M byceps/blueprints/board/views.py => byceps/blueprints/board/views.py +1 -1
@@ 17,7 17,7 @@ from ...services.text_markup.service import get_smileys, render_html
from ...services.user_badge import service as badge_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content_with_location

from ..authorization.decorators import permission_required

M byceps/blueprints/board_admin/views.py => byceps/blueprints/board_admin/views.py +1 -1
@@ 12,7 12,7 @@ from ...services.board import category_service as board_category_service
from ...services.brand import service as brand_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/brand_admin/views.py => byceps/blueprints/brand_admin/views.py +1 -1
@@ 14,7 14,7 @@ from ...services.orga import service as orga_service
from ...services.party import service as party_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to

from ..authorization.decorators import permission_required

M byceps/blueprints/news/views.py => byceps/blueprints/news/views.py +1 -1
@@ 10,7 10,7 @@ from flask import abort, current_app, g

from ...services.news import service as news_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated


blueprint = create_blueprint('news', __name__)

M byceps/blueprints/news_admin/views.py => byceps/blueprints/news_admin/views.py +1 -1
@@ 14,7 14,7 @@ from ...services.brand import service as brand_service
from ...services.news import service as news_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to

from ..authorization.decorators import permission_required

M byceps/blueprints/newsletter_admin/views.py => byceps/blueprints/newsletter_admin/views.py +1 -1
@@ 14,7 14,7 @@ from ...services.brand import service as brand_service
from ...services.newsletter import service as newsletter_service
from ...services.newsletter.types import SubscriptionState
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import jsonified, textified

from ..authorization.decorators import permission_required

M byceps/blueprints/orga_admin/views.py => byceps/blueprints/orga_admin/views.py +1 -1
@@ 17,7 17,7 @@ from ...services.user import service as user_service
from ...util.export import serialize_to_csv
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content, textified

from ..authorization.decorators import permission_required

M byceps/blueprints/orga_presence/views.py => byceps/blueprints/orga_presence/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort
from ...services.orga_presence import service as orga_presence_service
from ...services.party import service as party_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated

from ..authorization.decorators import permission_required
from ..authorization.registry import permission_registry

M byceps/blueprints/orga_team/views.py => byceps/blueprints/orga_team/views.py +1 -1
@@ 13,7 13,7 @@ from flask import g
from ...services.orga_team import service as orga_team_service
from ...services.user import service as user_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated


Orga = namedtuple('Orga', ['user', 'full_name', 'team_name', 'duties'])

M byceps/blueprints/orga_team_admin/views.py => byceps/blueprints/orga_team_admin/views.py +1 -1
@@ 13,7 13,7 @@ from ...services.party import service as party_service
from ...services.user import service as user_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/party/views.py => byceps/blueprints/party/views.py +1 -1
@@ 12,7 12,7 @@ from ...config import get_current_party_id
from ...services.party import service as party_service
from ...services.ticketing import attendance_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated


blueprint = create_blueprint('party', __name__)

M byceps/blueprints/party_admin/views.py => byceps/blueprints/party_admin/views.py +1 -1
@@ 15,7 15,7 @@ from ...services.shop.order import service as order_service
from ...services.ticketing import ticket_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to

from ..authorization.decorators import permission_required

M byceps/blueprints/seating/views.py => byceps/blueprints/seating/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort, g
from ...config import get_ticket_management_enabled
from ...services.seating import area_service as seating_area_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated


blueprint = create_blueprint('seating', __name__)

M byceps/blueprints/seating_admin/views.py => byceps/blueprints/seating_admin/views.py +1 -1
@@ 14,7 14,7 @@ from ...services.seating import \
    category_service as seating_category_service, \
    seat_group_service, seat_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated

from ..authorization.decorators import permission_required
from ..authorization.registry import permission_registry

M byceps/blueprints/shop/orders/views.py => byceps/blueprints/shop/orders/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort, g
from ....services.shop.order import service as order_service
from ....services.user import service as user_service
from ....util.framework.blueprint import create_blueprint
from ....util.templating import templated
from ....util.framework.templating import templated

from ...authentication.decorators import login_required


M byceps/blueprints/shop_article_admin/views.py => byceps/blueprints/shop_article_admin/views.py +1 -1
@@ 19,7 19,7 @@ from ...services.shop.sequence import service as sequence_service
from ...services.ticketing import ticket_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/shop_order/views.py => byceps/blueprints/shop_order/views.py +1 -1
@@ 15,7 15,7 @@ from ...services.shop.order.models.order import PaymentMethod
from ...services.shop.order import service as order_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to

from ..authentication.decorators import login_required

M byceps/blueprints/shop_order_admin/views.py => byceps/blueprints/shop_order_admin/views.py +1 -1
@@ 17,7 17,7 @@ from ...services.shop.sequence import service as sequence_service
from ...services.user import service as user_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/snippet_admin/views.py => byceps/blueprints/snippet_admin/views.py +1 -1
@@ 14,7 14,7 @@ from ...util.datetime.format import format_datetime_short
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.iterables import pairwise
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/style_guide/views.py => byceps/blueprints/style_guide/views.py +1 -1
@@ 7,7 7,7 @@ byceps.blueprints.style_guide.views
"""

from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated


blueprint = create_blueprint('style_guide', __name__)

M byceps/blueprints/terms/views.py => byceps/blueprints/terms/views.py +1 -1
@@ 12,7 12,7 @@ from ...services.terms import service as terms_service
from ...services.verification_token import service as verification_token_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to

from .forms import ConsentForm

M byceps/blueprints/terms_admin/views.py => byceps/blueprints/terms_admin/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort
from ...services.brand import service as brand_service
from ...services.terms import service as terms_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated

from ..authorization.decorators import permission_required
from ..authorization.registry import permission_registry

M byceps/blueprints/ticketing/views.py => byceps/blueprints/ticketing/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort, g
from ...services.ticketing import ticket_service
from ...util.framework.blueprint import create_blueprint
from ...util.iterables import find
from ...util.templating import templated
from ...util.framework.templating import templated


blueprint = create_blueprint('ticketing', __name__)

M byceps/blueprints/ticketing_admin/views.py => byceps/blueprints/ticketing_admin/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort, request
from ...services.party import service as party_service
from ...services.ticketing import ticket_bundle_service, ticket_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated

from ..authorization.decorators import permission_required
from ..authorization.registry import permission_registry

M byceps/blueprints/tourney/views.py => byceps/blueprints/tourney/views.py +1 -1
@@ 10,7 10,7 @@ from flask import abort, g, request, url_for

from ...services.tourney import service as tourney_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import respond_created

from . import signals

M byceps/blueprints/tourney_admin/views.py => byceps/blueprints/tourney_admin/views.py +1 -1
@@ 12,7 12,7 @@ from ...services.party import service as party_service
from ...services.tourney import service as tourney_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/user/views.py => byceps/blueprints/user/views.py +1 -1
@@ 22,7 22,7 @@ from ...services.user_badge import service as badge_service
from ...services.verification_token import service as verification_token_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_notice, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import create_empty_json_response, redirect_to

from .forms import DetailsForm, RequestConfirmationEmailForm, UserCreateForm

M byceps/blueprints/user_admin/views.py => byceps/blueprints/user_admin/views.py +1 -1
@@ 19,7 19,7 @@ from ...services.user_activity import service as activity_service
from ...services.user_badge import service as badge_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import respond_no_content

from ..authorization.decorators import permission_required

M byceps/blueprints/user_avatar/views.py => byceps/blueprints/user_avatar/views.py +1 -1
@@ 13,7 13,7 @@ from ...services.user_avatar import service as avatar_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_success
from ...util.image.models import ImageType
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to, respond_no_content

from .forms import UpdateForm

M byceps/blueprints/user_badge/views.py => byceps/blueprints/user_badge/views.py +1 -1
@@ 11,7 11,7 @@ from flask import abort, g
from ...services.user import service as user_service
from ...services.user_badge import service as badge_service
from ...util.framework.blueprint import create_blueprint
from ...util.templating import templated
from ...util.framework.templating import templated


blueprint = create_blueprint('user_badge', __name__)

M byceps/blueprints/user_group/views.py => byceps/blueprints/user_group/views.py +1 -1
@@ 11,7 11,7 @@ from flask import g, request
from ...services.user_group import service as user_group_service
from ...util.framework.blueprint import create_blueprint
from ...util.framework.flash import flash_error, flash_success
from ...util.templating import templated
from ...util.framework.templating import templated
from ...util.views import redirect_to

from .forms import CreateForm

A byceps/util/framework/templating.py => byceps/util/framework/templating.py +76 -0
@@ 0,0 1,76 @@
"""
byceps.util.framework.templating
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Templating utilities

:Copyright: 2006-2017 Jochen Kupperschmidt
:License: Modified BSD, see LICENSE for details.
"""

from functools import wraps
from typing import Callable, Optional

from flask import render_template


_TEMPLATE_FILENAME_EXTENSION = '.html'


def templated(arg) -> Callable:
    """Decorate a callable to wrap its return value in a template and that in
    a response object.

    This decorator expects the decorated callable to return a dictionary of
    objects that should be added to the template context, or ``None``.

    The name of the template to render can be either specified as argument or,
    if not present, will be determined by concatenating the callable's module
    and function object name (format: 'module_callable').

    The rendered template string will be wrapped in a ``Response`` object and
    returned.
    """
    def decorator(f: Callable, template_name: Optional[str]=None):
        @wraps(f)
        def decorated(*args, **kwargs):
            name = _get_template_name(f, template_name)

            context = f(*args, **kwargs)

            if context is None:
                context = {}
            elif not isinstance(context, dict):
                return context

            return render_template(name, **context)
        return decorated

    if hasattr(arg, '__call__'):
        return decorator(arg)

    def wrapper(f: Callable):
        return decorator(f, arg)

    return wrapper


def _get_template_name(view_function: Callable, template_name: Optional[str]) \
                      -> str:
    if template_name is None:
        name = _derive_template_name(view_function)
    else:
        name = template_name

    return name + _TEMPLATE_FILENAME_EXTENSION


def _derive_template_name(view_function: Callable) -> str:
    """Derive the template name from the view function's module and name."""
    # Select segments between `byceps.blueprints.` and `.views`.
    module_package_name_segments = view_function.__module__.split('.')
    blueprint_path_segments = module_package_name_segments[2:-1]

    action_name = view_function.__name__

    return '/'.join(blueprint_path_segments + [action_name])

M byceps/util/templating.py => byceps/util/templating.py +2 -66
@@ 2,82 2,18 @@
byceps.util.templating
~~~~~~~~~~~~~~~~~~~~~~

Templating utilities.
Templating utilities

:Copyright: 2006-2017 Jochen Kupperschmidt
:License: Modified BSD, see LICENSE for details.
"""

from functools import wraps
from typing import Any, Callable, Dict, Optional
from typing import Any, Dict, Optional

from flask import render_template
from jinja2 import BaseLoader, Environment, FunctionLoader, Template
from jinja2.sandbox import ImmutableSandboxedEnvironment


TEMPLATE_FILENAME_EXTENSION = '.html'


def templated(arg) -> Callable:
    """Decorate a callable to wrap its return value in a template and that in
    a response object.

    This decorator expects the decorated callable to return a dictionary of
    objects that should be added to the template context, or ``None``.

    The name of the template to render can be either specified as argument or,
    if not present, will be determined by concatenating the callable's module
    and function object name (format: 'module_callable').

    The rendered template string will be wrapped in a ``Response`` object and
    returned.
    """
    def decorator(f: Callable, template_name: Optional[str]=None):
        @wraps(f)
        def decorated(*args, **kwargs):
            name = _get_template_name(f, template_name)

            context = f(*args, **kwargs)

            if context is None:
                context = {}
            elif not isinstance(context, dict):
                return context

            return render_template(name, **context)
        return decorated

    if hasattr(arg, '__call__'):
        return decorator(arg)

    def wrapper(f: Callable):
        return decorator(f, arg)

    return wrapper


def _get_template_name(view_function: Callable, template_name: Optional[str]) \
                      -> str:
    if template_name is None:
        name = _derive_template_name(view_function)
    else:
        name = template_name

    return name + TEMPLATE_FILENAME_EXTENSION


def _derive_template_name(view_function: Callable) -> str:
    """Derive the template name from the view function's module and name."""
    # Select segments between `byceps.blueprints.` and `.views`.
    module_package_name_segments = view_function.__module__.split('.')
    blueprint_path_segments = module_package_name_segments[2:-1]

    action_name = view_function.__name__

    return '/'.join(blueprint_path_segments + [action_name])


def load_template(source: str, *, template_globals: Dict[str, Any]=None):
    """Load a template from source, using the sandboxed environment."""
    env = create_sandboxed_environment()