1

I'm currently learning Django forms and I came across this post.

One of the forms currently looks like this:

form screenshot

What I'd like to do is to change Category into a formset and be able to render multiple dropdowns while creating a product.

My models.py:

class Category(models.Model):
    name = models.CharField(max_length=30)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=30)
    price = models.DecimalField(decimal_places=2, max_digits=10)
    category = models.ForeignKey(Category, on_delete = models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

My forms.py:

class CategoryForm(forms.ModelForm):
    class Meta:
        model = Category
        fields = ('name', )


class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ('name', 'price', 'category', )

    def __init__(self, user, *args, **kwargs):
        super(ProductForm, self).__init__(*args, **kwargs)
        self.fields['category'].queryset = Category.objects.filter(user=user)

Current method in views.py:

@login_required
def new_product(request):
    if request.method == 'POST':
        form = ProductForm(request.user, request.POST)
        if form.is_valid():
            product = form.save(commit=False)
            product.user = request.user
            product.save()
            return redirect('products_list')
    else:
        form = ProductForm(request.user)
    return render(request, 'products/product_form.html', {'form': form})

products_form.html:

{% extends 'base.html' %}

{% block content %}
  <h1>New product</h1>
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="save">
    <a href="{% url 'products_list' %}">cancel</a>
  </form>
{% endblock %}

What I tried is to make use of the modelformset_factory and change the method in views.py by creating a CategoryFormSet as:

CategoryFormSet = modelformset_factory(Category, fields=('name', ), extra=2)
formset = CategoryFormSet(data=data, queryset=Category.objects.filter(user=request.user))

then replacing the original form from views.py with the created formset. In the html I simply replace the {{form}} with {{formset}}. After playing around with it for a while, I either get the New product with just a submit button (no form rendered) or a User object has no attribute GET error. What am I doing wrong?

Rosi98
  • 107
  • 9

2 Answers2

0

The tutorial focuses on allowing the user to add/update more instances of one model. You want to edit one thing, with multiple related things inline.

However, your data model only allows one category per product, so this does not make any sense. Whether you want more than one category per product, is something only you can answer :) - I'm going to assume you want that.

First you need to change your model to allow for multiple categories per product:

class Product(models.Model):
    name = models.CharField(max_length=30)
    price = models.DecimalField(decimal_places=2, max_digits=10)
    categories = models.ManyToManyField(Category, related_name='products')
    user = models.ForeignKey(User, on_delete=models.CASCADE)

And then you need to learn about Inline Formsets.

Come back with a specific if you get stuck on that.

  • But can I create inline formsets for many to many fields? [link](https://stackoverflow.com/questions/10302403/django-inlineformset-factory-and-manytomany-fields) – Rosi98 Jul 28 '20 at 10:47
  • Wow, such bad accepted answers still exist on SO :(. The trick is to use the through model, because that contains the foreign key. I'll update my answer. –  Jul 28 '20 at 10:55
-1

Instead of creating new model Category. You can do this.

CATEGORY_CHOICES= ( 
    ("1", "1"), 
    ("2", "2"), 
    ("3", "3"), 
    ("4", "4"), 
    ("5", "5"), 
    ("6", "6"), 
    ("7", "7"), 
    ("8", "8"), 
)
category = models.CharField(max_length = 20,choices = CATEGORY_CHOICES,default = '1')

It will automatically render in HTML.

  • I want to get the categories from the database, not to hardcode them – Rosi98 Jul 28 '20 at 10:14
  • This ties categories to the code, so needs code changes to add new categories. It doesn't automatically render in HTML, in fact, how to handle choice labels and values in templates is often asked. –  Jul 28 '20 at 10:14