Ejemplo n.º 1
0
        def _wrapped_view(request, *args, **kwargs):
            # if more than one parameter is passed to the decorator we try to
            # fetch object for which check would be made
            obj = None
            if lookup_variables:
                project_type, project_key = lookup_variables[
                    0], lookup_variables[1]
                project_key_value = kwargs[project_key]
                project_type_value = kwargs[project_type]
                # Parse model
                if project_type_value in settings.G3WADMIN_PROJECT_APPS:
                    model = apps.get_model(project_type_value, 'Project')
                else:
                    raise GuardianError("{} no in G3W_PROJECT_APPS: ".format(
                        project_type_value, settings.G3WADMIN_PROJECT_APPS))

                if project_key_value.isdigit():
                    lookup_dict = {'pk': int(project_key_value)}
                else:
                    lookup_dict = {'slug': project_key_value}
                obj = get_object_or_404(model, **lookup_dict)

            # ad app to perm
            perms = [project_type_value + "." + perm]

            response = get_40x_or_None(request,
                                       perms=perms,
                                       obj=obj,
                                       login_url=login_url,
                                       redirect_field_name=redirect_field_name,
                                       return_403=return_403,
                                       accept_global_perms=accept_global_perms)
            if response:
                return response
            return view_func(request, *args, **kwargs)
Ejemplo n.º 2
0
def permissions_required(
    request,
    model,
    instance=None,
    return_403=True,
    accept_global_perms=True,
    create=False,
    perms=None,
):
    # 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 we're doing a creation we should have permission to create the object.
        if create:
            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 has_permission is not None:
        return has_permission
Ejemplo n.º 3
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
Ejemplo n.º 4
0
    def update(self, request, **kwargs):
        """Update the metadata of a version."""
        version: Version = self.get_object()

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   version.dandiset,
                                   return_403=True)
        if response:
            return response

        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        version_metadata: VersionMetadata
        version_metadata, created = VersionMetadata.objects.get_or_create(
            name=serializer.validated_data['name'],
            metadata=serializer.validated_data['metadata'])

        if created:
            version_metadata.save()

        version.metadata = version_metadata
        version.save()

        serializer = VersionDetailSerializer(instance=version)
        return Response(serializer.data, status=status.HTTP_200_OK)
Ejemplo n.º 5
0
    def users(self, request, dandiset__pk):
        dandiset = get_object_or_404(Dandiset, pk=dandiset__pk)
        if request.method == 'PUT':
            # Verify that the user is currently an owner
            response = get_40x_or_None(request, ['owner'],
                                       dandiset,
                                       return_403=True)
            if response:
                return response

            serializer = UserSerializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)

            def get_user_or_400(username):
                try:
                    return User.objects.get(username=username)
                except User.DoesNotExist:
                    raise ValidationError(f'User {username} not found')

            owners = [
                get_user_or_400(username=owner['username'])
                for owner in serializer.validated_data
            ]
            if len(owners) < 1:
                raise ValidationError('Cannot remove all draft owners')

            removed_owners, added_owners = dandiset.set_owners(owners)
            dandiset.save()

            send_ownership_change_emails(dandiset, removed_owners,
                                         added_owners)

        serializer = UserSerializer(dandiset.owners, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
Ejemplo n.º 6
0
    def check_permissions(self, request):
        """
        Checks if *request.user* has all permissions returned by
        *get_required_permissions* method.

        :param request: Original request.
        """
        obj = self.get_permission_object()

        forbidden = get_40x_or_None(request,
                                    perms=self.get_required_permissions(
                                        request),
                                    obj=obj,
                                    login_url=self.login_url,
                                    redirect_field_name=self.redirect_field_name,
                                    return_403=self.return_403,
                                    return_404=self.return_404,
                                    accept_global_perms=self.accept_global_perms,
                                    any_perm=self.any_perm,
                                    )
        if forbidden:
            self.on_permission_check_fail(request, forbidden, obj=obj)
        if forbidden and self.raise_exception:
            raise PermissionDenied()
        return forbidden
Ejemplo n.º 7
0
        def _wrapped_view(*args, **kwargs):
            # View functions take request as the first argument,
            # but view methods take (self, request) as the first two arguments.
            # This correctly identifies the request in either case,
            # and provides a meaningful error message if no request is found.
            for arg in args[:2]:
                if isinstance(arg, HttpRequest):
                    request = arg
                    break
            else:
                raise TypeError("%s() missing 1 required positional argument: "
                                "'request'" % view_func.__name__)
            # if more than one parameter is passed to the decorator we try to
            # fetch object for which check would be made
            obj = None
            if lookup_variables:
                model, lookups = lookup_variables[0], lookup_variables[1:]
                # Parse model
                if isinstance(model, str):
                    splitted = model.split('.')
                    if len(splitted) != 2:
                        raise GuardianError(
                            "If model should be looked up from "
                            "string it needs format: 'app_label.ModelClass'")
                    model = apps.get_model(*splitted)
                elif issubclass(model.__class__, (Model, ModelBase, QuerySet)):
                    pass
                else:
                    raise GuardianError(
                        "First lookup argument must always be "
                        "a model, string pointing at app/model or queryset. "
                        "Given: %s (type: %s)" % (model, type(model)))
                # Parse lookups
                if len(lookups) % 2 != 0:
                    raise GuardianError(
                        "Lookup variables must be provided "
                        "as pairs of lookup_string and view_arg")
                lookup_dict = {}
                for lookup, view_arg in zip(lookups[::2], lookups[1::2]):
                    if view_arg not in kwargs:
                        raise GuardianError("Argument %s was not passed "
                                            "into view function" % view_arg)
                    lookup_dict[lookup] = kwargs[view_arg]
                obj = get_object_or_404(model, **lookup_dict)

            response = get_40x_or_None(request,
                                       perms=[perm],
                                       obj=obj,
                                       login_url=login_url,
                                       redirect_field_name=redirect_field_name,
                                       return_403=return_403,
                                       return_404=return_404,
                                       accept_global_perms=accept_global_perms)
            if response:
                return response
            return view_func(*args, **kwargs)
Ejemplo n.º 8
0
    def users(self, request, dandiset__pk):
        dandiset = self.get_object()
        if request.method == 'PUT':
            # Verify that the user is currently an owner
            response = get_40x_or_None(request, ['owner'],
                                       dandiset,
                                       return_403=True)
            if response:
                return response

            serializer = UserSerializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)

            def get_user_or_400(username):
                # SocialAccount uses the generic JSONField instead of the PostGres JSONFIELD,
                # so it is not empowered to do a more powerful query like:
                # SocialAccount.objects.get(extra_data__login=username)
                # We resort to doing this awkward search instead.
                for social_account in SocialAccount.objects.filter(
                        extra_data__icontains=username):
                    if social_account.extra_data['login'] == username:
                        return social_account.user
                else:
                    try:
                        return User.objects.get(username=username)
                    except ObjectDoesNotExist:
                        raise ValidationError(f'User {username} not found')

            owners = [
                get_user_or_400(username=owner['username'])
                for owner in serializer.validated_data
            ]
            if len(owners) < 1:
                raise ValidationError('Cannot remove all draft owners')

            removed_owners, added_owners = dandiset.set_owners(owners)
            dandiset.save()

            send_ownership_change_emails(dandiset, removed_owners,
                                         added_owners)

        owners = []
        for owner in dandiset.owners:
            try:
                owner = SocialAccount.objects.get(user=owner)
                owner_dict = {'username': owner.extra_data['login']}
                if 'name' in owner.extra_data:
                    owner_dict['name'] = owner.extra_data['name']
                owners.append(owner_dict)
            except SocialAccount.DoesNotExist:
                # Just in case some users aren't using social accounts, have a fallback
                owners.append({
                    'username': owner.username,
                    'name': f'{owner.first_name} {owner.last_name}'
                })
        return Response(owners, status=status.HTTP_200_OK)
Ejemplo n.º 9
0
    def destroy(self, request, dandiset__pk):
        dandiset: Dandiset = get_object_or_404(Dandiset, pk=dandiset__pk)

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   dandiset,
                                   return_403=True)
        if response:
            return response

        dandiset.delete()
        return Response(None, status=status.HTTP_204_NO_CONTENT)
Ejemplo n.º 10
0
    def destroy(self, request, versions__dandiset__pk, versions__version, **kwargs):
        asset = self.get_object()
        version = Version.objects.get(
            dandiset__pk=versions__dandiset__pk, version=versions__version
        )

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'], version.dandiset, return_403=True)
        if response:
            return response

        version.assets.remove(asset)
        return Response(None, status=status.HTTP_204_NO_CONTENT)
 def _wrapped_view(request, *args, **kwargs):
     filters = {}
     for kwarg, kwvalue in list(kwargs.items()):
         if kwarg in ignore:
             continue
         filters[kwarg] = kwvalue
     obj = get_object_or_404(klass, **filters)
     response = get_40x_or_None(request,
                                perms=[perm],
                                obj=obj,
                                return_403=True)
     if response:
         return response
     return view_func(request, *args, **kwargs)
 def _wrapped_view(request, *args, **kwargs):
     filters = {}
     for kwarg, kwvalue in list(kwargs.items()):
         if kwarg in ignore:
             continue
         filters[kwarg] = kwvalue
     obj = get_object_or_404(klass, **filters)
     response = get_40x_or_None(
         request,
         perms=[perm],
         obj=obj,
         return_403=True,
     )
     if response:
         return response
     return view_func(request, *args, **kwargs)
Ejemplo n.º 13
0
    def update(self, request, versions__dandiset__pk, versions__version, **kwargs):
        """Update the metadata of an asset."""
        old_asset = self.get_object()
        version = Version.objects.get(
            dandiset__pk=versions__dandiset__pk,
            version=versions__version,
        )

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'], version.dandiset, return_403=True)
        if response:
            return response

        serializer = AssetRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        asset_blob = get_object_or_404(AssetBlob, blob_id=serializer.validated_data['blob_id'])

        metadata = serializer.validated_data['metadata']
        if 'path' not in metadata:
            return Response('No path specified in metadata', status=404)
        path = metadata['path']
        asset_metadata, created = AssetMetadata.objects.get_or_create(metadata=metadata)
        if created:
            asset_metadata.save()

        if asset_metadata == old_asset.metadata and asset_blob == old_asset.blob:
            # No changes, don't create a new asset
            new_asset = old_asset
        else:
            # Mint a new Asset whenever blob or metadata are modified
            new_asset = Asset(
                path=path,
                blob=asset_blob,
                metadata=asset_metadata,
                previous=old_asset,
            )
            new_asset.save()

            # Replace the old asset with the new one
            version.assets.add(new_asset)
            version.assets.remove(old_asset)

        serializer = AssetDetailSerializer(instance=new_asset)
        return Response(serializer.data, status=status.HTTP_200_OK)
Ejemplo n.º 14
0
        def _wrapped_view(request, *args, **kwargs):
            # if more than one parameter is passed to the decorator we try to
            # fetch object for which check would be made
            obj = None
            if lookup_variables:
                model, lookups = lookup_variables[0], lookup_variables[1:]
                # Parse model
                if isinstance(model, str):
                    splitted = model.split('.')
                    if len(splitted) != 2:
                        raise GuardianError(
                            "If model should be looked up from "
                            "string it needs format: 'app_label.ModelClass'")
                    model = apps.get_model(*splitted)
                elif issubclass(model.__class__, (Model, ModelBase, QuerySet)):
                    pass
                else:
                    raise GuardianError(
                        "First lookup argument must always be "
                        "a model, string pointing at app/model or queryset. "
                        "Given: %s (type: %s)" % (model, type(model)))
                # Parse lookups
                if len(lookups) % 2 != 0:
                    raise GuardianError(
                        "Lookup variables must be provided "
                        "as pairs of lookup_string and view_arg")
                lookup_dict = {}
                for lookup, view_arg in zip(lookups[::2], lookups[1::2]):
                    if view_arg not in kwargs:
                        raise GuardianError("Argument %s was not passed "
                                            "into view function" % view_arg)
                    lookup_dict[lookup] = kwargs[view_arg]
                obj = get_object_or_404(model, **lookup_dict)

            response = get_40x_or_None(request,
                                       perms=[perm],
                                       obj=obj,
                                       login_url=login_url,
                                       redirect_field_name=redirect_field_name,
                                       return_403=return_403,
                                       return_404=return_404,
                                       accept_global_perms=accept_global_perms)
            if response:
                return response
            return view_func(request, *args, **kwargs)
Ejemplo n.º 15
0
    def create(self, request, versions__dandiset__pk, versions__version):
        version: Version = get_object_or_404(
            Version,
            dandiset=versions__dandiset__pk,
            version=versions__version,
        )

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'], version.dandiset, return_403=True)
        if response:
            return response

        serializer = AssetRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        asset_blob = get_object_or_404(AssetBlob, blob_id=serializer.validated_data['blob_id'])

        metadata = serializer.validated_data['metadata']
        if 'path' not in metadata:
            return Response('No path specified in metadata.', status=400)
        path = metadata['path']
        asset_metadata, created = AssetMetadata.objects.get_or_create(metadata=metadata)
        if created:
            asset_metadata.save()

        if version.assets.filter(path=path, blob=asset_blob, metadata=asset_metadata).exists():
            return Response('Asset already exists.', status=status.HTTP_400_BAD_REQUEST)

        asset = Asset(
            path=path,
            blob=asset_blob,
            metadata=asset_metadata,
        )
        asset.save()
        version.assets.add(asset)

        serializer = AssetDetailSerializer(instance=asset)
        return Response(serializer.data, status=status.HTTP_200_OK)
Ejemplo n.º 16
0
    def publish(self, request, **kwargs):
        old_version = self.get_object()

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   old_version.dandiset,
                                   return_403=True)
        if response:
            return response

        new_version = Version.copy(old_version)

        new_version.doi = doi.create_doi(new_version)

        new_version.save()
        for asset in old_version.assets.all():
            new_version.assets.add(asset)

        new_version.write_yamls()

        serializer = VersionSerializer(new_version)
        return Response(serializer.data, status=status.HTTP_200_OK)
Ejemplo n.º 17
0
        def _wrapped_view(request, *args, **kwargs):
            # if more than one parameter is passed to the decorator we try to
            # fetch object for which check would be made
            obj = None
            if lookup_variables:
                model, lookups = lookup_variables[0], lookup_variables[1:]
                # Parse model
                if isinstance(model, basestring):
                    splitted = model.split('.')
                    if len(splitted) != 2:
                        raise GuardianError("If model should be looked up from "
                                            "string it needs format: 'app_label.ModelClass'")
                    model = apps.get_model(*splitted)
                elif issubclass(model.__class__, (Model, ModelBase, QuerySet)):
                    pass
                else:
                    raise GuardianError("First lookup argument must always be "
                                        "a model, string pointing at app/model or queryset. "
                                        "Given: %s (type: %s)" % (model, type(model)))
                # Parse lookups
                if len(lookups) % 2 != 0:
                    raise GuardianError("Lookup variables must be provided "
                                        "as pairs of lookup_string and view_arg")
                lookup_dict = {}
                for lookup, view_arg in zip(lookups[::2], lookups[1::2]):
                    if view_arg not in kwargs:
                        raise GuardianError("Argument %s was not passed "
                                            "into view function" % view_arg)
                    lookup_dict[lookup] = kwargs[view_arg]
                obj = get_object_or_404(model, **lookup_dict)

            response = get_40x_or_None(request, perms=[perm], obj=obj,
                                       login_url=login_url, redirect_field_name=redirect_field_name,
                                       return_403=return_403, return_404=return_404, accept_global_perms=accept_global_perms)
            if response:
                return response
            return view_func(request, *args, **kwargs)
Ejemplo n.º 18
0
    def check_permissions(self, request):
        """
        Checks if *request.user* has all permissions returned by
        *get_required_permissions* method.

        :param request: Original request.
        """
        obj = self.get_permission_object()

        forbidden = get_40x_or_None(request,
                                    perms=self.get_required_permissions(
                                        request),
                                    obj=obj,
                                    login_url=self.login_url,
                                    redirect_field_name=self.redirect_field_name,
                                    return_403=self.return_403,
                                    return_404=self.return_404,
                                    accept_global_perms=self.accept_global_perms
                                    )
        if forbidden:
            self.on_permission_check_fail(request, forbidden, obj=obj)
        if forbidden and self.raise_exception:
            raise PermissionDenied()
        return forbidden
Ejemplo n.º 19
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)
Ejemplo n.º 20
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)
Ejemplo n.º 21
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)
Ejemplo n.º 22
0
    def generic_permissions(
        self,
        request,
        model,
        pk=None,
        instance=None,
        post_save_redirect=None,
        templates=None,
        staff_only=None,
        max_checkboxes=None,
        **extra_context
    ):
        # avoid circular import
        from touchtechnology.common.forms.auth import permissionformset_factory

        if instance is None:
            assert pk is not None
            instance = get_object_or_404(model, pk=pk)

        perms = get_perms_for_model(model, add=True)
        has_permission = get_40x_or_None(
            request,
            perms,
            instance,
            return_403=True,
            accept_global_perms=True,
        )

        if has_permission and not request.user.is_superuser:
            return has_permission

        extra_context.update(
            {
                "model": model,
            }
        )

        content_type = ContentType.objects.get_for_model(model)
        queryset = Permission.objects.filter(content_type=content_type).exclude(
            codename__startswith="add_"
        )
        formset_class = permissionformset_factory(
            model, staff_only=staff_only, max_checkboxes=max_checkboxes
        )

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

        return self.generic_edit_multiple(
            request,
            model,
            queryset=queryset,
            formset_class=formset_class,
            formset_kwargs={
                "instance": instance,
                "queryset": queryset,
            },
            post_save_redirect=post_save_redirect,
            templates=templates,
            extra_context=extra_context,
        )
Ejemplo n.º 23
0
def upload_initialize_view(request: Request) -> HttpResponseBase:
    """
    Initialize a multipart upload.

    A list of parts will be returned, each of which has a presigned upload URL and a size.
    This URL communicates directly with the object store so the client can upload bytes directly.

    https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html
    """
    request_serializer = UploadInitializationRequestSerializer(data=request.data)
    request_serializer.is_valid(raise_exception=True)
    content_size = request_serializer.validated_data['contentSize']
    digest = request_serializer.validated_data['digest']
    if digest['algorithm'] != 'dandi:dandi-etag':
        return Response('Unsupported Digest Type', status=400)
    etag = digest['value']
    dandiset_id = request_serializer.validated_data['dandiset']
    dandiset = get_object_or_404(
        Dandiset.objects.visible_to(request.user),
        id=dandiset_id,
    )
    response = get_40x_or_None(request, ['owner'], dandiset, return_403=True)
    if response:
        return response

    logging.info(
        'Starting upload initialization of size %s, ETag %s to dandiset %s',
        content_size,
        etag,
        dandiset,
    )

    asset_blobs = AssetBlob.objects.filter(etag=etag)
    if asset_blobs.exists():
        return Response(
            'Blob already exists.',
            status=status.HTTP_409_CONFLICT,
            headers={'Location': asset_blobs.first().blob_id},
        )
    elif dandiset.embargo_status != Dandiset.EmbargoStatus.OPEN:
        embargoed_asset_blobs = EmbargoedAssetBlob.objects.filter(dandiset=dandiset, etag=etag)
        if embargoed_asset_blobs.exists():
            return Response(
                'Blob already exists.',
                status=status.HTTP_409_CONFLICT,
                headers={'Location': embargoed_asset_blobs.first().blob_id},
            )
    logging.info('Blob with ETag %s does not yet exist', etag)

    if dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN:
        upload, initialization = Upload.initialize_multipart_upload(etag, content_size, dandiset)
    else:
        upload, initialization = EmbargoedUpload.initialize_multipart_upload(
            etag, content_size, dandiset
        )
    logging.info('Upload of ETag %s initialized', etag)
    upload.save()
    logging.info('Upload of ETag %s saved', etag)

    response_serializer = UploadInitializationResponseSerializer(initialization)
    logging.info('Upload of ETag %s serialized', etag)
    return Response(response_serializer.data)