Map selected radio button to existing post category: Cannot assign "'Artikel'": "Post.category" must be a "Category" instance

Viewed 68

I want to map radio buttons to given categories. One way is to do this with the help of a choice field. I don't know if this is a good approach.

I have seen many other articles and SO-posts but they did not point me to a direction from where I could see my mistake - so after many hours of research I wanted to ask you here. Thanks!

models.py

class Category(models.Model):
    title = models.CharField(max_length=255, verbose_name="Kategorie")
    ...

class Post(models.Model):
    title = models.CharField(max_length=150)
    ....
    category = models.ForeignKey(Category, verbose_name="Kategorie", 

forms.py

CAT_CHOICES= [
    ('Artikel', 'Artikel'),
    ('Videos', 'Videos'),
    ('Anderes', 'Anderes'),
    ]

class PostForm(forms.ModelForm):
    category = forms.ChoiceField(widget=forms.RadioSelect, choices=CAT_CHOICES)
    class Meta:
        model = Post # is this throwing an error because I only handle the 'Post' model?
        fields = [
            'title',
             ...
            'category',
        ]

views.py

def function_name(request):
    ...
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid(): # error happens here

template.html:

<label>Artikel</label>
<input id="category0" type="radio" name="category" value="Artikel">
<label>Videos</label>
<input id="category1" type="radio" name="category" value="Videos">
<label>Anderes</label>
<input id="category2" type="radio" name="category" value="Anderes">

output:


ValueError at /

Cannot assign "'Artikel'": "Post.category" must be a "Category" instance.

error message

What would be a good practice to handle this?

I am thankful for any advice.


SOLUTION

Even if I have spent a lot of time looking for my mistake I could not find it. With the two first answers I got pointed to the right direction. I have adjusted my template file and the form:

CAT_CHOICES= [
    (1, 'Artikel'),
    (2, 'Videos'),
    (3, 'Anderes'),
    ]

class PostForm(forms.ModelForm):
    category = forms.ModelChoiceField(queryset=Category.objects.all())
    class Meta:
        model = Post
        fields = [
            'title',
             ...
            'category',
        ]

and

<label>Artikel</label>
<input id="category0" type="radio" name="category" value="1">
<label>Videos</label>
<input id="category1" type="radio" name="category" value="2">
<label>Anderes</label>
<input id="category2" type="radio" name="category" value="3">

Where the value is according to the id of the category. It would be interesting to know how to link the actual title and not the id.

Thank you!


UPDATE 2

As pointed by Matt the additional choices variable is not necessary. Thanks a lot it works!

2 Answers

You probably need to reference your Category instances by their id values. That would mean changing the choices in your forms.py to something like:

CAT_CHOICES= [
    (1, 'Artikel'),
    (2, 'Videos'),
    (3, 'Anderes'),
]

If you have hard-coded those options in your template, they would need to be changes to something like:

<label>Artikel</label>
<input id="category0" type="radio" name="category" value="1">
<label>Videos</label>
<input id="category1" type="radio" name="category" value="2">
<label>Anderes</label>
<input id="category2" type="radio" name="category" value="3">

UPDATE

The CAT_CHOICES are only required if you need to limit which options are displayed or validated.

You also don't need to redefine ModelChoiceField, as this is the default form field for a ForeignKey. This should work:

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = [
            'title',
             ...
            'category',
        ]

If you don't want to hard-code the options in your template, you can allow the form to display all Categories as options. You will need to specify the RadioSelect widget in your form:

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = [
            'title',
             ...
            'category',
        ]
        widgets = {
            'category': forms.RadioSelect(),
        }

Then in your template, replace what you have posted with the {{ form.category }} tag (assuming form is the context variable name you are using). More info about how to control display of a RadioSelect widget can be found in the widget docs.

I don't think you can achieve this properly with a ModelForm.

Use as normal Form instead like:

class PostForm(forms.Form):
    category = forms.ModelChoiceField(queryset=Category.objects.all())

I haven't tested this with your code, but that's how I would go about it.

Related