btc-action-set 0.2.3
Features for managing template elements depending on the project role and permission system.
License: BSD License

===================================================
BTC Action Set
===================================================

Features for managing template elements depending on the project role and permission system.

Detailed documentation is in the "docs" directory.

Quick start
-----------

1. Add "action_set" to your INSTALLED_APPS setting like this::

      INSTALLED_APPS = (
          ...
          'action_set',
      )

2. `ActionSet`. Prepare base action-set, for example we'll use built-in `ActionSetWithPermissionCheck`::

    class BaseActionSet(ActionSetWithPermissionCheck):

        protect_css_class: str = 'protected-element'
        elements_permissions = {
            'action_1': ['change_status'],  # - for search in user permissions
            'action_2': ['edit']
        }

        def get_user_permissions(self) -> list:
            return User.objects.filter(pk=self.request.user.pk).values_list('user_roles__permissions__name', flat=True)

        def check_permissions(self) -> None:
            if not self.request.user.is_superuser:
                return super().check_permissions()

3. Write custom action-set::

    class MyActionSet(BaseActionSet):

        template = 'action_set/my_action_set.html'

        action_1 = ActionButton(
            'Action 1', css_classes=['js-post_content_btn'],
            html_params={
                'type': 'button',
                'data-url': reverse('app:action_1'),
            }
        )
        action_2 = ActionButton(
            'Action 2', css_classes=['js-load_content_btn'],
            html_params={
                'type': 'button'
            }
        )

        def __init__(self, instance, *args, **kwargs)
            self.instance = instance  # type: MyModel
            super().__init__(*args, **kwargs)
            self.actions['action_2'].html_params.update({'data-url': reverse('app:action_2', self.instance.pk)})

4. Write view, use `ActionSetMixinView` to construct view::

    class MyViewWithActionSet(ActionSetMixinView, TemplateView):
        ...
        template_name = 'my_view_template.html'
        action_set_class = MyActionSet
        ...

        def get_action_set_kwargs(self, **kwargs) -> dict:
            return super().get_action_set_kwargs(instance=get_object_or_404(MyModel, pk=self.kwargs['pk']), **kwargs)

5. Prepare template::

    <!-- action_set/my_action_set.html -->
    ...
    <div class="action-set action-set__my-action-set">
        <div class="action-set__info">
            <i class="material-icons">landscape</i>
            <span>Action Set Test</span>
        </div>
        <div class="action-set__toolbar">
            {% for action in action_set %}
                {{ action }}
            {% endfor %}
        </div>
    </div>
    ...

6. In the template of the view call `render` method::

    <!-- my_view_template.html -->
    ...
    <div>{{ action_set.render }}</div>
    ...

7. `ActionSetGroup`. Setup group and instantiate action-sets::

    class CustomActionSetGroup(ActionSetGroup):

        action_set_1 = MyActionSet1
        action_set_2 = MyActionSet2

8. Use `ActionSetGroupMixinView` to construct custom mixin and view like we did it with `ActionSetMixinView`.
   Next setup is same as for the `ActionSet`::

    class MyView(CustomActionSetGroupMixinView, TemplateView):

        action_set_group_class = CustomActionSetGroup
        template_name = 'my_awesome_page.html'

9. In template::

    ...
    <div>
        {{ action_set_group.action_set_1.render }}
    </div>
    <div>
        {{ action_set_group.action_set_2.render }}
    </div>
    ...

10 `ActionSetMenuMixin`. You can building menus by using `ActionSetMenuMixin` (automatic tabs highlight support).
Note: prepare template as was shown for the simple ActionSet::

    class MyDetailsMenu(ActionSetMenuMixin, ActionSet or use ActionSetWithPermissionCheck):

        url_attribute_name: str = 'data-url'       # attribute for comparing with current request.path
        activate_class: str = 'active'             # class for active button
        template = 'action_set/details_menu.html'

        menu_tab_1 = ActionButton(
            'TAB1', css_classes=['js-menu-btn'],
            html_params={
                'type': 'button',
                'data-container': '.js-my_content',
                'data-url': reverse_lazy('app:tab_1')
            }
        )
        menu_tab_2 = ActionButton(
            'TAB2', css_classes=['js-menu-btn'],
            html_params={
                'type': 'button',
                'data-container': '.js-my_content',
                'data-url': reverse_lazy('app:tab_2')
            }
        )
        # ... etc.

11 `BreadcrumbsSet`. You can use this Action Set for building breadcrumbs. Prepare custom `BreadcrumbsSet`::

    class MyBreadcrumbsSet(BreadcrumbsSet):

        template = 'my_breadcrumbs.html'
        active_crumb_css_class = 'active-breadcrumb'     # for highlight

        crumb1 = ActionButton(
            'Crumb1',
            html_params={
                'data-url': reverse_lazy('app:tab1'),
                'data-container': '.js-dispatcher-content'
            }
        )
        crumb2 = ActionButton(
            'Crumb2',
            html_params={
                'data-url': reverse_lazy('app:tab2'),
                'data-container': '.js-dispatcher-content'
            }
        )
        # ... etc.

12 Prepare yor view for showing breadcrumbs - use `BreadcrumbsMixinView`::

    class MyView(BreadcrumbsMixinView, TemplateView):

        breadcrumbs_set_class = MyBreadcrumbsSet
        template_name = 'my_awesome_page.html'

        breadcrumbs_set = ['crumb1', '*crumb2']  # use '*' symbol for showing active breadcrumb.


13 Prepare template::

    <!-- my_breadcrumbs.html -->

    {% load dev_tools %}

    <div class="action-set action-set__breadcrumbs">
        {% for action in action_set %}
            {{ action|tweak_parameter:'class: mdl-button' }}
            {% if not forloop.last %}
                <span class="breadcrumbs-separator">></span>
            {% endif %}
        {% endfor %}
    </div>