Exemple #1
0
    def generic_delete(
        self,
        request,
        model_or_manager,
        pk,
        post_delete_redirect=None,
        perms=None,
        permission_required=False,
        accept_global_perms=True,
        return_403=None,
        **kwargs
    ):

        model, manager = model_and_manager(model_or_manager)

        instance = get_object_or_404(manager, pk=pk)

        # If not explicitly declared, redirect the user to login if they are
        # currently anonymous. If they are already identified, we can safely
        # return an HTTP 403 Forbidden.
        if return_403 is None:
            return_403 = not request.user.is_anonymous

        # Implement permission checking for specified object instance, throw
        # an HTTP 403 (using django-guardian configuration) when the user is
        # not entitled to edit the instance.
        if permission_required:

            # If no perms are specified, build sensible default using built in
            # permission types that come batteries included with Django.
            if perms is None:
                perms = get_perms_for_model(model, delete=True)

            # Determine the user's permission to edit this object using the
            # get_40x_or_None - saves decorating view method with
            # @permission_required_or_403
            has_permission = get_40x_or_None(
                request,
                perms,
                obj=instance,
                return_403=return_403,
                accept_global_perms=accept_global_perms,
            )

            # If permission is denied, return the response. May already have
            # thrown an exception by now.
            if has_permission is not None:
                return has_permission

        if post_delete_redirect is None:
            redirect_to = urljoin(request.path, "../..")
            post_delete_redirect = self.redirect(redirect_to)

        instance.delete()
        messages.add_message(
            request,
            messages.SUCCESS,
            _("The %s has been deleted.") % model._meta.verbose_name,
        )
        return post_delete_redirect
Exemple #2
0
def create_permission_checker(model_or_manager, user):
    """
    Create an :class:`ObjectPermissionChecker` for optimal permission checking
    of related objects.

    http://django-guardian.readthedocs.io/en/stable/userguide/performance.html
    """
    model, manager = model_and_manager(model_or_manager)
    checker = ObjectPermissionChecker(user)
    try:
        checker.prefetch_perms(manager)
    except UnboundLocalError:
        logger.exception(
            "https://github.com/django-guardian/django-guardian/issues/519")
    return checker
Exemple #3
0
def has_permission(obj, user):
    try:
        model, manager = model_and_manager(obj)
    except NotModelManager:
        logger.exception('error="model cannot be determined"')
        return set()

    logger.debug(
        'model="%s.%s", object_id="%s", user="******"',
        model._meta.app_label,
        model._meta.model_name,
        getattr(obj, "pk", ""),
        user.get_username(),
    )

    # Calculate the permissions this user has for the given object, both
    # directly and inferred by group memberships.
    perms = user.get_all_permissions(obj)
    perms |= user.get_group_permissions(obj)

    # What are the permissions that this model accepts?
    model_perms = get_all_perms_for_model_cached(model, ttl=300)

    # Superusers have all permissions, so add each permission that the model
    # accepts for this user to the set that has been explicitly cast.
    if user.is_superuser:
        perms.update(model_perms)

    # Otherwise we need to iterate the groups that the user belongs to and see
    # if they transfer permission to the user.
    else:
        for group in user.groups.all():
            group_perms = group.permissions.all()
            group_model_perms = [
                p.codename for p in model_perms if p in group_perms
            ]
            logger.debug(
                'user="******", group="%s", permissions="%s"',
                user.get_username(),
                group,
                ", ".join(group_model_perms),
            )
            perms.update(group_model_perms)

    # Log the resulting set of permissions for this user for this object.
    logger.debug('user="******", permissions=%r', user.get_username(), perms)

    return perms
Exemple #4
0
    def generic_detail(
        self, request, model_or_manager, templates=None, extra_context=None, **kwargs
    ):
        model, manager = model_and_manager(model_or_manager)
        instance = get_object_or_404(manager, **kwargs)

        context = {
            "object": instance,
            model._meta.model_name: instance,
            "model": manager.none(),
        }
        context.update(extra_context or {})

        if templates is None:
            templates = self.template_path("detail.html", model._meta.model_name)

        return self.render(request, templates, context)
Exemple #5
0
    def generic_edit_related(
        self,
        request,
        model_or_manager,
        related_model,
        extra_context={},
        form_class=None,
        form_kwargs={},
        form_fields="__all__",
        form_widgets=None,
        formset_class=None,
        formset_kwargs={},
        formset_fields="__all__",
        instance=None,
        pk=None,
        post_save_callback=lambda o: None,
        post_save_redirect_args=(),
        post_save_redirect_kwargs={},
        post_save_redirect="index",
        post_save_related_callback=lambda o: None,
        templates=None,
    ):

        model, manager = model_and_manager(model_or_manager)

        if instance is None and pk is not None:
            instance = get_object_or_404(manager, pk=pk)

        # If the developer has not provided a custom form, then dynamically
        # construct a default ModelForm for them.
        if form_class is None:
            meta_class = type(
                smart_str("Meta"),
                (),
                {"model": model, "fields": form_fields, "widgets": form_widgets},
            )
            form_class = type(
                smart_str("EditForm"), self.model_form_bases, {"Meta": meta_class}
            )

        # If the developer has not provided a custom formset, then dynamically
        # construct a default one with inlineformset_factory.
        if formset_class is None:
            formset_class = inlineformset_factory(
                model, related_model, fields=formset_fields, extra=0
            )

        # Add form and formset prefixes if not already present in the kwargs
        form_kwargs.setdefault("prefix", "form")
        formset_kwargs.setdefault("prefix", "formset")

        # Keep verbose names for model and related_model for use in messages
        model_verbose_name = model._meta.verbose_name.lower()
        related_verbose_name_plural = related_model._meta.verbose_name_plural.lower()
        verbose = {
            "model_verbose_name": model_verbose_name,
            "related_verbose_name_plural": related_verbose_name_plural,
        }

        # Vanilla form processing here, take the post data and files, create
        # an instance of form_class, save and redirect.
        if request.method == "POST":
            sid = transaction.savepoint()

            form = form_class(
                data=request.POST, files=request.FILES, instance=instance, **form_kwargs
            )

            if form.is_valid():
                obj = form.save()
                logger.debug("Saved %r to database.", obj)

                # Rebuild formset with updated instance object.
                formset = formset_class(
                    data=request.POST,
                    files=request.FILES,
                    instance=obj,
                    **formset_kwargs
                )

                if formset.is_valid():
                    logger.debug(
                        "Related formset is valid, if we don't "
                        "complete successfully then an exception "
                        "will bubble up."
                    )
                    related = formset.save()
                    post_save_callback(obj)
                    for each in related:
                        post_save_related_callback(each)
                    transaction.savepoint_commit(sid)
                    messages.add_message(
                        request,
                        messages.SUCCESS,
                        _(
                            "Your %(model_verbose_name)s and "
                            "%(related_verbose_name_plural)s "
                            "have been saved."
                        )
                        % verbose,
                    )
                    if isinstance(post_save_redirect, HttpResponse):
                        return post_save_redirect
                    redirect_to = self.reverse(
                        post_save_redirect,
                        args=post_save_redirect_args,
                        kwargs=post_save_redirect_kwargs,
                    )
                    return redirect(redirect_to)

                # if we've made it out without returning a HttpRedirect then
                # we should not be persisting the result.
                logger.debug("ROLLBACK, we were not redirected.")
                transaction.savepoint_rollback(sid)

            else:
                formset = formset_class(
                    data=request.POST,
                    files=request.FILES,
                    instance=instance,
                    **formset_kwargs
                )

        else:
            form = form_class(instance=instance, **form_kwargs)
            formset = formset_class(instance=instance, **formset_kwargs)

        context = {
            "model": manager.none(),
            "form": form,
            "formset": formset,
            "object": instance,
            "media": form.media + formset.media,
            # For template compatibility, we don't use related ourselves but this
            # method is only used in touchtechnology.content.admin
            "related": (),
        }
        context.update(extra_context)

        if templates is None:
            templates = self.template_path("edit.related.html", model._meta.model_name)

        return self.render(request, templates, context)
Exemple #6
0
    def generic_edit_multiple(
        self,
        request,
        model_or_manager,
        extra_context=None,
        formset_class=None,
        formset_kwargs=None,
        formset_fields="__all__",
        post_save_callback=lambda o: None,
        post_save_redirect_args=None,
        post_save_redirect_kwargs=None,
        post_save_redirect=None,
        templates=None,
        changed_messages=None,
        perms=None,
        permission_required=False,
        accept_global_perms=True,
        return_403=None,
        *args,
        **kwargs
    ):

        if extra_context is None:
            extra_context = {}

        if formset_kwargs is None:
            formset_kwargs = {}

        if post_save_redirect_args is None:
            post_save_redirect_args = ()

        if post_save_redirect_kwargs is None:
            post_save_redirect_kwargs = {}

        if changed_messages is None:
            changed_messages = (
                (messages.SUCCESS, _("Your {models} have been saved.")),
            )

        # If not explicitly declared, redirect the user to login if they are
        # currently anonymous. If they are already identified, we can safely
        # return an HTTP 403 Forbidden.
        if return_403 is None:
            return_403 = not request.user.is_anonymous

        # Try and introspect the model if possible.
        model, manager = model_and_manager(model_or_manager)
        queryset = manager.all()

        # Implement permission checking for list of objects, will filter the
        # list to those objects the user is entitled to work on if we do not
        # throw an HTTP 403 for no permission.
        if permission_required:
            if perms is None:
                perms = get_all_perms_for_model_cached(model)

            # Determine the user's permission to see the list using the
            # get_40x_or_None - saves decorating view method with
            # @permission_required_or_403
            has_permission = get_40x_or_None(
                request,
                perms,
                return_403=return_403,
                accept_global_perms=accept_global_perms,
                any_perm=True,
            )

            # If the user does not have any global permissions then adjust the
            # queryset to return only the objects they can act on.
            if not any([request.user.has_perm(p) for p in perms]):
                queryset = get_objects_for_user(
                    request.user, perms, queryset, use_groups=True, any_perm=True
                )

            # If permission is denied, return the response. May already have
            # thrown an exception by now.
            if not queryset and has_permission is not None:
                return has_permission

            queryset = get_objects_for_user(
                request.user, perms, queryset, any_perm=True
            )

        # If queryset is not already specified as a keyword argument to the
        # formset attach it now. Value in formset_kwargs will take precedence.
        queryset = formset_kwargs.pop("queryset", queryset)

        # If the developer has not provided a custom formset, then dynamically
        # construct a default ModelFormSet for them.
        if formset_class is None:
            formset_class = modelformset_factory(model, extra=0, fields=formset_fields)

        if post_save_redirect is None:
            redirect_to = urljoin(request.path, "..")
            post_save_redirect = self.redirect(redirect_to)

        # Vanilla formset processing here, take the post data and files, create
        # an instance of formset_class, save and redirect.
        if request.method == "POST":
            formset = formset_class(
                data=request.POST,
                files=request.FILES,
                queryset=queryset,
                **formset_kwargs
            )
            if formset.is_valid():
                replace = dict(
                    model=smart_str(model._meta.verbose_name),
                    models=smart_str(model._meta.verbose_name_plural),
                )
                objects = formset.save()
                for each in objects:
                    post_save_callback(each)
                for level, message in changed_messages:
                    messages.add_message(request, level, message.format(**replace))
                if isinstance(post_save_redirect, HttpResponse):
                    return post_save_redirect
                redirect_to = self.reverse(
                    post_save_redirect,
                    args=post_save_redirect_args,
                    kwargs=post_save_redirect_kwargs,
                )
                return redirect(redirect_to)
        else:
            formset = formset_class(queryset=queryset, **formset_kwargs)

        context = {
            "model": manager.none(),
            "formset": formset,
            "object_list": queryset,
        }
        context.update(extra_context)

        if templates is None:
            templates = self.template_path("edit_multiple.html", model._meta.model_name)
            templates.append("edit_multiple.html")

        return self.render(request, templates, context)
Exemple #7
0
    def generic_edit(
        self,
        request,
        model_or_manager,
        pk=None,
        instance=None,
        form_class=None,
        form_kwargs=None,
        form_fields="__all__",
        form_widgets=None,
        post_save_callback=lambda o: None,
        post_save_redirect=None,
        templates=None,
        changed_messages=((messages.SUCCESS, _("Your {model} has been " "saved.")),),
        unchanged_messages=((messages.INFO, _("Your {model} was not changed.")),),
        permission_required=False,
        perms=None,
        accept_global_perms=True,
        return_403=None,
        related=None,
        extra_context=None,
    ):

        model, manager = model_and_manager(model_or_manager)

        # If not explicitly declared, redirect the user to login if they are
        # currently anonymous. If they are already identified, we can safely
        # return an HTTP 403 Forbidden.
        if return_403 is None:
            return_403 = not request.user.is_anonymous

        if instance is None and pk is not None:
            instance = get_object_or_404(manager, pk=pk)

        if form_kwargs is None:
            form_kwargs = {}

        if extra_context is None:
            extra_context = {}

        if post_save_redirect is None:
            redirect_to = urljoin(request.path, "..") if pk is None else request.path
            post_save_redirect = self.redirect(redirect_to)

        # Implement permission checking for specified object instance, throw
        # an HTTP 403 (using django-guardian configuration) when the user is
        # not entitled to edit the instance.
        if permission_required:

            # If no perms are specified, build sensible default using built in
            # permission types that come batteries included with Django.
            if perms is None:
                perms = get_perms_for_model(model, change=True)

                # When there is no pk value, we're doing a creation and should
                # have permission to create the object.
                if pk is None or instance.pk is None:
                    perms = get_perms_for_model(model, add=True)

            # Determine the user's permission to edit this object using the
            # get_40x_or_None - saves decorating view method with
            # @permission_required_or_403
            has_permission = get_40x_or_None(
                request,
                perms,
                obj=instance,
                return_403=return_403,
                accept_global_perms=accept_global_perms,
            )

            # If permission is denied, return the response. May already have
            # thrown an exception by now.
            if has_permission is not None:
                return has_permission

        # If the developer has not provided a custom form, then dynamically
        # construct a default ModelForm for them.
        if form_class is None:
            meta_class = type(
                smart_str("Meta"),
                (),
                {"model": model, "fields": form_fields, "widgets": form_widgets},
            )
            form_class = type(
                smart_str("EditForm"), self.model_form_bases, {"Meta": meta_class}
            )

        # Whether we've dynamically constructed our form_class or not, check to
        # ensure that we've inherited from all the bases. Log when we haven't,
        # but don't raise any exceptions.
        for base in self.model_form_bases:
            if not issubclass(form_class, base):
                logger.error('"%s" does not inherit "%s"', form_class, base)

        # Pass the instance to the form constructor if this is a ModelForm
        # subclass, otherwise it will need to be explicitly added to
        # form_kwargs if expected.
        if issubclass(form_class, BaseModelForm) and instance is not None:
            form_kwargs.setdefault("instance", instance)

        # Vanilla form processing here, take the post data and files, create
        # an instance of form_class, save and redirect.
        if request.method == "POST":
            form = form_class(data=request.POST, files=request.FILES, **form_kwargs)
            if form.is_valid():
                replace = dict(
                    model=smart_str(model._meta.verbose_name),
                    models=smart_str(model._meta.verbose_name_plural),
                )
                if form.has_changed():
                    obj = form.save()
                    callback_res = post_save_callback(obj)
                    for level, message in changed_messages:
                        messages.add_message(request, level, message.format(**replace))
                    if callback_res is not None:
                        return callback_res
                else:
                    for level, message in unchanged_messages:
                        messages.add_message(request, level, message.format(**replace))
                return post_save_redirect
        else:
            form = form_class(**form_kwargs)

        if templates is None:
            templates = self.template_path("edit.html", model._meta.model_name)
            templates.append("edit.html")

        context = {
            "model": manager.none(),
            "template": select_template_name(templates),
            "form": form,
            "object": instance,
            "related": related,
            "cancel_url": post_save_redirect.url,
        }
        context.update(extra_context or {})

        return self.render(request, templates, context)
Exemple #8
0
    def generic_list(
        self,
        request,
        model_or_manager,
        extra_context=None,
        object_list_name=None,
        paginate_by=None,
        queryset=None,
        templates=None,
        list_templates=None,
        perms=None,
        permission_required=False,
        accept_global_perms=True,
        return_403=None,
        search=None,
        *args,
        **kwargs
    ):

        model, manager = model_and_manager(model_or_manager)

        # If not explicitly declared, redirect the user to login if they are
        # currently anonymous. If they are already identified, we can safely
        # return an HTTP 403 Forbidden.
        if return_403 is None:
            return_403 = not request.user.is_anonymous

        if queryset is None:
            queryset = manager.all()

        # Search should be an iterable of fields to look into.
        if search is not None:
            terms = request.GET.get("search")
            if terms is not None:
                queryset = queryset.annotate(
                    search=SearchVector(*search),
                ).filter(search=terms)

        # Implement permission checking for list of objects, will filter the
        # list to those objects the user is entitled to work on if we do not
        # throw an HTTP 403 for no permission.
        if permission_required:
            if perms is None:
                perms = get_all_perms_for_model_cached(model)

            # Determine the user's permission to see the list using the
            # get_40x_or_None - saves decorating view method with
            # @permission_required_or_403
            has_permission = get_40x_or_None(
                request,
                perms,
                return_403=return_403,
                accept_global_perms=accept_global_perms,
                any_perm=True,
            )

            global_perms = any([request.user.has_perm(p) for p in perms])

            # If the user does not have any global permissions then adjust the
            # queryset to return only the objects they can act on.
            if not global_perms:
                queryset = get_objects_for_user(
                    request.user, perms, queryset, use_groups=True, any_perm=True
                )

            # If permission is denied, return the response. May already have
            # thrown an exception by now.
            if not queryset and has_permission is not None:
                return has_permission

        if extra_context is None:
            extra_context = {}

        if object_list_name is None:
            object_list_name = "{0}_list".format(model._meta.model_name)

        if templates is None:
            templates = self.template_path("list.html", model._meta.model_name)

        if list_templates is None:
            list_templates = self.template_path("list.inc.html", model._meta.model_name)
            # Always tack the mvp theme version on the end as a fallback.
            list_templates.append("mvp/list.inc.html")

        # Determine the namespace of this Application, so we can construct
        # view names to pass to the template engine to reverse CRUD URI's.
        create_url_name = "%s:add" % request.resolver_match.namespace
        create_url_kwargs = request.resolver_match.kwargs.copy()
        create_url_kwargs.pop("admin", None)
        create_url_kwargs.pop("component", None)

        # We can't leave this until the template is rendered because it throws
        # exceptions (stupid!) so we'll avoid lazy evaluation and pass None if
        # the create view doesn't exist for this model.
        try:
            create_url = reverse(
                create_url_name,
                args=request.resolver_match.args,
                kwargs=create_url_kwargs,
            )
        except NoReverseMatch:
            create_url = None

        context = {
            "queryset": queryset,
            object_list_name: queryset,
            "object_list": queryset,
            "is_paginated": False,
            "page": None,
            "page_num": None,
            "paginator": None,
            "model": manager.none(),
            "template": select_template_name(templates),
            "list_template": select_template_name(list_templates),
            # Is search enabled?
            "searchable": bool(search),
            "search": request.GET.get("search", ""),
            # Pass to template the name permissions, so we can re-use template
            # code to generically list and add/change/delete objects
            "add_perm": "%s.add_%s" % (model._meta.app_label, model._meta.model_name),
            "view_perm": "%s.view_%s" % (model._meta.app_label, model._meta.model_name),
            "change_perm": "%s.change_%s"
            % (model._meta.app_label, model._meta.model_name),
            "delete_perm": "%s.delete_%s"
            % (model._meta.app_label, model._meta.model_name),
            "create_url": create_url,
        }

        if paginate_by is None:
            paginate_by = PAGINATE_BY

        # Ensure that paginate_by is an integer to prevent ZeroDivisionError
        # inside the Django paginator steps.
        paginate_by = int(paginate_by)

        if paginate_by > 0:
            try:
                page_num = int(request.GET.get("page", 1))
            except TypeError:
                page_num = 1

            paginator = Paginator(queryset, paginate_by)

            try:
                page = paginator.page(page_num)
            except EmptyPage:
                page = paginator.page(paginator.num_pages)

            context.update(
                {
                    object_list_name: page.object_list,
                    "object_list": page.object_list,
                    "is_paginated": paginator.num_pages > 1,
                    "page": page,
                    "page_num": page_num,
                    "paginator": paginator,
                }
            )

        context.update(extra_context)
        return self.render(request, templates, context, *args, **kwargs)
Exemple #9
0
def mvp_list(context, queryset, scope=None, template=None):
    model, manager = model_and_manager(queryset)

    request = context.get("request")
    component = context.get("component")

    if scope is None:
        scope = model._meta.model_name

    # Use the template_path resolution method of the AdminComponent to find
    # the right list for inclusion. Use "list.inc.html" by default because
    # "list.html" is reserved for use by generic_list view.
    if template is None:
        paths = component.template_path("list.inc.html",
                                        model._meta.model_name)
        # Ensure there is a sensible default so we always find something
        paths.append("mvp/list.inc.html")
        logger.debug("template_search=%r", paths)
        template = loader.select_template(paths)
        template_name = template.template.name
        logger.debug("template_search_result=%r", template_name)

    match = resolve(request.path)
    namespace = match.namespace
    if match.namespace.rsplit(":", 1)[1] != scope:
        namespace = "%s:%s" % (match.namespace, scope)

    perms = get_all_perms_for_model_cached(model)
    global_perms = any([request.user.has_perm(p) for p in perms])

    # If the user does not have any global permissions then adjust the
    # queryset to return only the objects they can act on.
    if not global_perms:
        queryset = get_objects_for_user(
            request.user,
            perms,
            queryset.select_related(),
            use_groups=True,
            any_perm=True,
        )

    # Tidy up the kwargs found using resolve
    kw = match.kwargs.copy()
    kw.pop("admin", None)
    kw.pop("component", None)

    context.update({
        "model":
        model,
        "template":
        template,
        "queryset":
        queryset,
        "object_list":
        queryset,
        "create":
        reverse_lazy("%s:add" % namespace, kwargs=kw),
        # Pass to template the name permissions so we can re-use template
        # code to generically list and add/change/delete objects
        "add_perm":
        "%s.add_%s" % (model._meta.app_label, model._meta.model_name),
        "view_perm":
        "%s.view_%s" % (model._meta.app_label, model._meta.model_name),
        "change_perm":
        "%s.change_%s" % (model._meta.app_label, model._meta.model_name),
        "delete_perm":
        "%s.delete_%s" % (model._meta.app_label, model._meta.model_name),
    })
    return context