qwix
qwix

Reputation: 251

Can not check form values with a ChoiceField

I am working on a catalog of classified grouped by categories.

However when I submit my form, I get the following error message:

Caught ValueError while rendering: Cannot assign "u'9'": "Classified.category" must be a "Category" instance.

I believe this is because Django expects a Category objects instead of a simple integer which corresponds to the chosen Category ID.

Here is how I wrote the system: A classified is linked to one category. The category system is hierarchical with a parent category and a list of child categories. For example I have something like this:

Electronics
    |-- IPad
    |-- IPods
    |-- ...

So I have the following models:

class Category(BaseModel):
    # [...]
    name = models.CharField(u'Name', max_length=50)
    slug = AutoSlugField(populate_from='name', slugify=slugify, unique=True,
        unique_with='name', max_length=255, default='')
    parent = models.IntegerField(u'parent', max_length=10, null=False,
            default=0)

    objects = CategoryManager()

    # [...]

class Classified(BaseModel):
    # [...]
    category = models.ForeignKey(Category, related_name='classifieds')

I created the following manager:

class CategoryManager(Manager):
    def categoryTree(self):
        tree = self.raw("SELECT"
            " P.id, P.name parent_name, P.slug parent_slug, P.id parent_id,"
            " C.name child_name, C.slug child_slug, C.id child_id"
            " FROM classified_category C"
            " LEFT JOIN classified_category P ON P.id = C.parent"
            " WHERE C.parent <> 0"
            " ORDER BY P.name, C.name;")

        categoryTree = []

        current_parent_id = tree[0].parent_id
        current_parent_name = tree[0].parent_name
        option_list = []

        for c in tree:
            if current_parent_id != c.parent_id:
                categoryTree.append((current_parent_name, option_list))
                option_list = []
                current_parent_id = c.parent_id
                current_parent_name = c.parent_name

            option_list.append((c.child_id, c.child_name))

        categoryTree.append((current_parent_name, option_list))

        return category

And my Django form contains the following:

class ClassifiedForm(forms.ModelForm):
    # [...]

    category = forms.ChoiceField(label=u'Category', required=True,
            choices=Category.objects.categoryTree(), widget=forms.Select())
    # [...]

If I use category = forms.ModelChoiceField(Category.objects.all()) everything works fine but I need to control how the <select> field is displayed with a list of <optgroup>. This is why use categoryTree() But unfortunately using CategoryManager.categoryTree() breaks my form validation and I do not know how to fix my problem.

If I could be pointed to where I was wrong and how I can fix this, that would be awesome.

Thanks in advance for your help.

Upvotes: 2

Views: 415

Answers (2)

Evan Brumley
Evan Brumley

Reputation: 2468

You can and should still use a ModelChoiceField. The list of choices can be modified in the init method of the form class - i.e.

class ClassifiedForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ClassifiedForm, self).__init__(*args, **kwargs)

        # Set the queryset for validation purposes. 
        # May not be necessary if categoryTree contains all categories
        self.fields['category'].queryset = Category.objects.categoryTreeObjects() 

        # Set the choices
        self.fields['category'].choices = Category.objects.categoryTree()

Also, you should look carefully at the django-mptt package. It looks like you may be reinventing the wheel here.

Upvotes: 0

DrTyrsa
DrTyrsa

Reputation: 31981

Quick solution is to save category manually

class ClassifiedForm(forms.ModelForm):
    # [...]

    category = forms.ChoiceField(label=u'Category', required=True,
            choices=Category.objects.categoryTree(), widget=forms.Select())

    class Meta:
        exclude=('category',)

    def save(self):
        classified = super(ClassifiedForm, self).save(commit=False)
        classified.category = Category.objects.get(id=self.cleaned_data['category'])
        classified.save()
        return classified

Upvotes: 1

Related Questions