[Answer]-Unable to get post data in a multipleChoiceField with django

0πŸ‘

βœ…

I end up with this code working:

class PostForm(ModelForm):
    class Meta:
        model = models.Post
        fields = ['text', 'products', 'image']
        widgets = {
            'text': Textarea(attrs={'cols': 80, 'rows': 10}),
            'products': SelectMultiple(),
        }

class PostView(AuthenticatedUserView):
    template_name = 'myapp/post.html'

    def get_if_authenticated(self, request, user):
        form = PostForm()
        return render(request, self.template_name, {"form": form})

    def post_if_authenticated(self, request, user):
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():
            post = form.save(commit=False)
            post.user = user
            post.save()
            return HttpResponseRedirect(reverse('myapp:actions'))
        return render(request, self.template_name, {"form": form})

And the template:

{% if form.errors %}<p><strong>{{ form.errors }}</strong></p>{% endif %}

<form method="post" enctype="multipart/form-data"  action="">
    {% csrf_token %}
    {% for field in form %}
        {{ field.label_tag }} {{ field }} <br>
    {% endfor %}
    <input type="submit" value="OK">
</form>
πŸ‘€Joan P.S

1πŸ‘

That form will never successfully validate, since choices must be a list of (id, name) pairs. You are sending a simple list of ids. Since you’re not displaying the form errors on the template, you hide the reason why the form is not saving.

However, your approach is flawed in several ways. Really you should be using a ModelMultipleChoiceField with a custom queryset, rather than setting choices directly. And you should be sending the invalid form to the template, and using that to display the fields and errors.

class PostForm(forms.Form):
  product = forms.ModelMultipleChoiceField(
    label='product',
    queryset=Post.objects.none())
  text = forms.CharField(label='text', max_length=1000)

  def __init__(self, *args, **kwargs):
    user = kwargs.pop('user')
    super(PostForm, self).__init__(*args, **kwargs)
    self.fields['product'].queryset = Post.objects.filter(user=user)


class PostView(AuthenticatedUserView):
  template_name = 'trendby/post.html'

  def get_if_authenticated(self, request, user):
    form = PostForm(user=user)
    return render(request, self.template_name, {'form': form})

  def post_if_authenticated(self, request, user):
    form = PostForm(request.POST, user=user)
    if form.is_valid():
        text = form.cleaned_data['text']
        post = models.Post(text=text, user=user)
        post.save()
        return HttpResponse("Post: " + text)

    return render(request, self.template_name, {'form': form})


<form action="" method="post" id="post_form">
  {% csrf_token %}
  {{ form.products.label_tag }}
  {{ form.products }}
  {{ form.products.errors }}
  <input type='submit'>
</form>

This code is much shorter, more idiomatic and provides validation feedback to the user.

To make it even shorter, you should look into the various editing class-based views which remove a lot of the form handling boilerplate.

0πŸ‘

After a while studying django documentation I took your advice and I updated the forms, now the code is cleaner:

The model:

class Post(models.Model):
    text = models.CharField(max_length=1000)
    user = models.ForeignKey(User)
    products = models.ManyToManyField(Product)

The From:

class PostForm(ModelForm):
    class Meta:
        model = models.Post
        fields = ['text', 'products']
        widgets = {
            'text': Textarea(attrs={'cols': 80, 'rows': 10}),
        }

And here the view:

class PostView(AuthenticatedUserView):
    template_name = 'trendby/post.html'

    def get_if_authenticated(self, request, user):
        PostSetForm = modelformset_factory(
            models.Post,
            form=PostForm)

        formset = PostSetForm(queryset=models.Post.objects.none())
        return render(request, self.template_name, {
            "form": formset[0],
        })

    def post_if_authenticated(self, request, user):
        PostFormSet = modelformset_factory(
            models.Post,
            form=PostForm)
        formset = PostFormSet(request.POST)
        if formset.is_valid():
            post = formset.save(commit=False)[0]
            post.user = user
            post.save()
            return HttpResponseRedirect(reverse('trendby:actions'))
        return render(request, self.template_name)

Finally, the template is changed:

<form method="post" action="">
    {% csrf_token %}
    {% for field in form %}
        {{ field.label_tag }} {{ field }}<br>
    {% endfor %}
    <input type="submit" value="OK">
</form>

Now django creates the product picker, but when I submit the form I get the following error:

Exception Type: ValidationError
Exception Value:    
['ManagementForm data is missing or has been tampered with']

And the post data only contains one id for product, and it should contain more than one:

form-0-id   
''
csrfmiddlewaretoken 
'FIWwI9VW48lOjZLd7yT6vqtpK2IZaJ1K'
form-0-products 
'2'
form-0-text 
''

How can I make it to send all the products and fix the ValidationError?

πŸ‘€Joan P.S

Leave a comment