# Permissions helper
# This class inspects the user and determines if they have a specific model permission

from django.contrib.auth import get_permission_codename
from django.contrib.auth.models import Permission


class PermissionsHelper(object):
    """
    Provides permission-related helper functions to help determine what a
    user can do with a 'typical' model (where permissions are granted
    model-wide), and to a specific instance of that model.
    """

    def __init__(self, model, inspect_view_enabled=False):
        self.model = model
        self.opts = model._meta
        self.inspect_view_enabled = inspect_view_enabled

    def get_all_model_permissions(self):
        """
        Return a queryset of all Permission objects pertaining to the `model` specified at initialisation.
        """

        return Permission.objects.filter(
            content_type__app_label=self.opts.app_label,
            content_type__model=self.opts.model_name,
        )

    def get_perm_codename(self, action):
        return get_permission_codename(action, self.opts)

    def user_has_specific_permission(self, user, perm_codename):
        """
        Call the provided django user's built-in `has_perm` method.
        """

        return user.has_perm("%s.%s" % (self.opts.app_label, perm_codename))

    def user_has_any_permissions(self, user):
        """
        Return a boolean to indicate whether `user` has any model-wide permissions
        """

        for perm in self.get_all_model_permissions().values('codename'):
            if self.user_has_specific_permission(user, perm['codename']):
                return True
        return False

    def user_can_list(self, user):
        """
        Return a boolean to indicate whether `user` is permitted on at least one action.
        """

        return self.user_has_any_permissions(user)

    def user_can_view(self, user):
        """
        Return a boolean to indicate whether `user` is permitted on at least one action 
        and if we are allowing a view perm.
        """

        return self.inspect_view_enabled and self.user_has_any_permissions(user)

    def user_can_create(self, user):
        """
        Return a boolean to indicate whether `user` is permitted to create new instances of `self.model`
        """

        perm_codename = self.get_perm_codename('add')
        return self.user_has_specific_permission(user, perm_codename)

    def user_can_edit(self, user):
        """
        Return a boolean to indicate whether `user` is permitted to 'change' a specific `self.model` instance.
        """

        perm_codename = self.get_perm_codename('change')
        return self.user_has_specific_permission(user, perm_codename)

    def user_can_delete(self, user):
        """
        Return a boolean to indicate whether `user` is permitted to 'delete' a specific `self.model` instance.
        """

        perm_codename = self.get_perm_codename('delete')
        return self.user_has_specific_permission(user, perm_codename)

##########################################################

# Generic view mixin
# Use on a generic view to test the permission

from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied

from users.permissions import PermissionsHelper


class PermissionRequiredMixin(LoginRequiredMixin):
    """ Check a user has the required permission before proceeding """

    # one of PermissionsHelper.user_can_* methods
    dispatch_requires_permission = None

    @property
    def permission_helper(self):
        return PermissionsHelper(model=self.model)

    def dispatch(self, request, *args, **kwargs):

        # ensure self.dispatch_requires_permission exists
        if not self.dispatch_requires_permission:
            raise NotImplementedError('Please define self.dispatch_requires_permission')

        # ensure value of self.dispatch_requires_permission is a method on the class PermissionsHelper
        if not hasattr(self.permission_helper, self.dispatch_requires_permission):
            raise AttributeError(
                'The class {} has no attribute {}'.format(self.permission_helper, self.dispatch_requires_permission)
            )

        # check user has the permission
        check = getattr(self.permission_helper, self.dispatch_requires_permission)(request.user)
        if not check:
            raise PermissionDenied

        return super(PermissionRequiredMixin, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        """ pass users permissions into the context to use in templates """

        context = super(PermissionRequiredMixin, self).get_context_data(**kwargs)
        context.update({
            'user_can_list': self.permission_helper.user_can_list(self.request.user),
            'user_can_view': self.permission_helper.user_can_view(self.request.user),
            'user_can_create': self.permission_helper.user_can_create(self.request.user),
            'user_can_edit': self.permission_helper.user_can_edit(self.request.user),
            'user_can_delete': self.permission_helper.user_can_delete(self.request.user),
        })

        return context


##########################################################

# Generic view example

from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView
from django.utils.translation import ugettext as _

from someapp.forms import CreateForm
from someapp.models import SomeModel
from .mixins import PermissionRequiredMixin


class SomeModelCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
    model = SomeModel
    form_class = CreateForm
    dispatch_requires_permission = 'user_can_create'
    template_name = 'someapp/somemodel/create.html'
    success_message = _("Created successfully")
    success_url = reverse_lazy('someapp:somemodel-list')

##########################################################

# Template usage
# In the template you can also do stuff like:

<ul>
    {% if user_can_create %}
    <li><a href="{% url 'someapp:somemodel-create' %}">{%  trans 'Add' %}</a></li>
    {% endif %}
    {% if user_can_view %}
    <li><a href="{% url 'someapp:somemodel-detail' pk %}">{%  trans 'View' %}</a></li>
    {% endif %}
    {% if user_can_edit %}
    <li><a href="{% url 'someapp:somemodel-update' pk %}">{%  trans 'Edit' %}</a></li>
    {% endif %}
</ul>