0

I'm receiving an array of up to N uuid4 values in the POST data for a SPA. I'm then supposed to be feeding into a view that deletes the specified object instances:

<QueryDict: {'uuids[]' : [
    'a1a241bd-147f-4672-86d7-1a6225b8080e'
    '791db3f0-5134-4927-b328-347c7261d7f6'
    'b8087a1b-710e-47b4-af5a-bae4d5e30532'
    '6d1a96b2-1b99-4707-a8e3-376a0d596d95'
    ...
]}>

As far as I can tell, it probably isn't the intent of MultiValueField to handle this data because I don't want to "compress" it. What's the django way of dealing with form data like this? Should I be manually trying to process request.POST myself and running each uuid through a form to validate it? Or overriding form.clean()?

2 Answers 2

1

I decided to go with a custom field and override post(). Would appreciate comments/edits if there's a better way.

forms.py

import uuid
from django.core.exceptions import ValidationError
MAX_UUIDS = 10
class UUIDArrayField(forms.Field):
    """ Custom field representing an array of up to MAX_UUIDS uuid4 strings """
    def clean(self, value):
        if type(value) != type([]):
            raise ValidationError(self.error_messages['not_a_list'])
        if len(value) > MAX_UUIDS:
            raise ValidationError(self.error_messages['too_many_values'])
        try:
            for v in value:
                print(v)
                uuid.UUID(v, version=4) # ValueError if not a valid uuid4
            return value
        except:
            raise ValidationError(self.error_messages['invalid_uuid'])


class ObjectMultiDeleteForm(forms.Form):
    """ form to delete multiple products """

    uuids = UUIDArrayField(
        error_messages = {
            'not_a_list': _("A data type other than list was supplied."),
            'too_many_values': _("The list of values exceeds the maximum allowed."),
            'invalid_uuid': _("An invalid identifier was specified."),
        })

views.py

from django.shortcuts import redirect
class ObjectMultiDelete(FormView):
    """ Deletes object instances from a list of their uuids """

    http_method_names = ['post',] #only accept POST requests
    success_url = reverse_lazy("objectlist")
    success_message = "%(message)s"
    form_class = ProductMultiDeleteForm

    def dispatch(self, request, *args, **kwargs):
        #access control goes here
        return super(ObjectMultiDelete, self).dispatch(request, *args, **kwargs)

    def get_success_message(self, cleaned_data):
        return self.success_message % dict(
            message = _("The specified objects were successfully deleted."),
        )

    def post(self, request, *args, **kwargs):
        post_data = request.POST.copy() #request.POST is immutable
        #because you can't name a form field 'uuids[]', but that's what you get when posting an array
        post_data['uuids'] = post_data.pop('uuids[]')
        form = ObjectMultiDeleteForm()
        form.data = post_data #bind form
        form.is_bound = True
        if form.is_valid():
            objects = Object.objects.filter(access_control_here)\
                            .filter(uuid__in=form.cleaned_data['uuids'])
            objects.delete()

            return redirect(self.success_url)
        else:
            #handle invalid form how you want
Sign up to request clarification or add additional context in comments.

Comments

0

In that case you're probably be best of with writing a small model that you can include at another place:

class UUIDItem(models.Model):
    request = models.PositiveIntegerField()
    uuid = models.UUID()

which you can easily put in relation with another model:

class UUIDContainer(models.Model):
    request = AutoField()

    @classmethod
    def add(self, uuid):
        if isinstance(uuid, list):
            for id in uuids:
                UUIDItem.objects.filter(request=self.request, uuid=id).create()
        else:
            UUIDItem.objects.filter(request=self.request, uuid=uuid).create()

    uuids = models.ForeignKey(
        UUIDRequest,
        on_delete=model.CASCADE,
        limit_choices_to={'request': self.request})

With this level of abstraction you can just easily list, modify and add UUID's to an existing request, or create a different "storage"

l = UUIDList()
l.add(your_uuids)
l.id # will print some id
UUIDItem.objects.filter(request=l.request).all()

Eh voila. Handles your list of UUID's.

1 Comment

Er, with the models.UUID() and UUIDRequest, UUIDList just appearing like that it's unclear as to how this helps me actually validate POST data and delete object instances? What does this code achieve?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.