Question

I am writing a simple form in Django which includes a ChoiceField allowing users to choose from a list of categories. For sure I want to pass the category_id to be processed. This is my code:

models.py:

class Category(models.Model):
      category = models.CharField(max_length=128)

      def __unicode__(self):
           return self.category


class Product(models.Model):
      code = models.CharField(max_length=75)
      name = models.CharField(max_length=128)
      price = models.DecimalField(max_digits=7, decimal_places=2)
      category = models.ForeignKey(Category)

      def __unicode__(self):
           return self.name

forms.py

class AddProductForm(forms.Form):
    category = forms.ChoiceField(label=_('Category'))
    product = forms.CharField(label=_('Product'), widget=forms.TextInput())
    code = forms.CharField(label=_('Code'), widget=forms.TextInput())
    price = forms.DecimalField(label=_('Price'))

Now in the views.py I fill in the choices:

def add_product_form(request):
    form = AddProductForm()

    form.fields['category'].choices =[(c.id, c.category) for c in Category.objects.all()] 

    return render_to_response('product-form.html', {'form':form})

Now everything seems to be okay except when I submit the form. It complains about the IDs of the Category. It says: Select a valid choice. 1 is not one of the available choices

This is how I am processing the form:

def add_product(request):
   if request.method == 'POST':
       form = AddProductForm(request.POST)

       if form.is_valid():
           category = request.cleaned_data['category']
           product = form.cleaned_data['product']
           code = form.cleaned_data['code']
           price = form.cleaned_data['price']

           product = Product(code=code, name=product, price=price, category_id=category)

           product.save()

           return HttpResponseRedirect('/ms-admin/')
       else:
           form = AddProductForm() # more is required here to fill the choices again

       return render_to_response('product-form.html', {'form':form})

I tried the same with TypedChoiceField but got the same invalid data. I know it has to do with the conversion between string and int and the unicode stuff. Can you please explain?

Was it helpful?

Solution

For some reason, you've separated the view that renders the form - including adding the choices - and the view that processes the submission. You don't add the choices on submission, so of course the values are not going to be valid.

You should get rid of the add_product_form view altogether. You could move the form.fields['category'].choices... bit into the add_product view, but a better solution is to use the form field that's specifically designed for getting choices from a model or queryset - ie ModelChoiceField.

Just use:

category = forms.ModelChoiceField(model=Category)

in your form definition.

OTHER TIPS

Thanks Daniel. I started learning Django not long time ago and I have this tendency to add a view to display a form, which is not necessary. So yes, I got rid of add_product_form and relied solely on add_product. As for the Category choice field now it is coded like this in the forms.py:

forms.py:

from django import forms
from django.utils.translation import ugettext_lazy as _

from myapp.models import Category

class AddProductForm(forms.Form):
    category = forms.ModelChoiceField(label=_('Category'), queryset=Category.objects.all())
    product = forms.CharField(label=_('Product'), widget=forms.TextInput())
    code = forms.CharField(label=_('Code'), widget=forms.TextInput())
    price = forms.DecimalField(label=_('Price'))

views.py:

def add_product(request):
   if request.method == 'POST':
      form = AddProductForm(request.POST)

      if form.is_valid():
         category = request.POST['category']
         product = form.cleaned_data['product']
         code = form.cleaned_data['code']
         price = form.cleaned_data['price']

         product = Product(code=code, name=product, price=price, category_id=category)

         product.save()

         # redirect to 'ms-admin/category_id' (the category to which belongs the newly added product
         return HttpResponseRedirect('/ms-admin/'+category)
     else:
         form = AddProductForm()

     return render_to_response('product-form.html', {'form':form})

I hope this helps newcomers to django. Please feel free to mention any ways of improving this above code ...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top