Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django models.CommaSeparatedIntegerField with forms.CheckboxSelectMultiple widget

I have a Django application and want to display multiple choice checkboxes in an Django's admin interface. I do not want to create a separate model for my choices by using a ManyToManyField.

models.py

from django.db import models

STAFF_BUSINESS_TYPES = {
    (1, "Foo"),
    (2, "Bar"),
    (3, "Cat"),
    (4, "Dog")
}

class Business(models.Model):
    name = models.CharField(max_length=255, unique=True)
    business_types = models.CommaSeparatedIntegerField(max_length=32, choices=STAFF_BUSINESS_TYPES)

forms.py

from business.models import Business, STAFF_BUSINESS_TYPES
from django.forms import CheckboxSelectMultiple, ModelForm, MultipleChoiceField

class BusinessForm(ModelForm):
    business_types = MultipleChoiceField(required=True, widget=CheckboxSelectMultiple, choices=STAFF_BUSINESS_TYPES)

    class Meta:
        model = Business
        fields = ['name', 'business_types']

    def clean_business_types(self):
        data = self.cleaned_data['business_types']
        cleaned_data = ",".join(data)
        return cleaned_data

admin.py

from django.contrib import admin
from business.models import Business
from business.forms import BusinessForm

@admin.register(Business)
class BusinessAdmin(admin.ModelAdmin):
    form = BusinessForm

However, when I attempt to add a business with type "Bar":

Select a valid choice. 1 is not one of the available choices.

Likewise with when I attempt to add a business with multiple values selected:

Select a valid choice. 1,2 is not one of the available choices.

How is 1 not a valid choice, considering (1, "Foo") is within my choices? Is it invalid to use Django's built in Comma Separated Integer field like this?

like image 563
Alexander Avatar asked Nov 26 '14 00:11

Alexander


People also ask

How can I have multiple models in a single Django ModelForm?

In a nutshell: Make a form for each model, submit them both to template in a single <form> , using prefix keyarg and have the view handle validation. If there is dependency, just make sure you save the "parent" model before dependant, and use parent's ID for foreign key before commiting save of "child" model.

What are widgets in Django forms?

A widget is Django's representation of an HTML input element. The widget handles the rendering of the HTML, and the extraction of data from a GET/POST dictionary that corresponds to the widget. The HTML generated by the built-in widgets uses HTML5 syntax, targeting <! DOCTYPE html> .

How do I override a form in Django?

You can override forms for django's built-in admin by setting form attribute of ModelAdmin to your own form class. See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form.

What is the difference between forms and model forms in Django?

The similarities are that they both generate sets of form inputs using widgets, and both validate data sent by the browser. The differences are that ModelForm gets its field definition from a specified model class, and also has methods that deal with saving of the underlying model to the database. Save this answer.


1 Answers

I've worked in a similar problem and here goes my solution:

# coding: utf-8
# python2 / django1.6.5

""" 
    That's a first solution I got to use CommaSeparatedIntegerFields with SelectMultiple widget. 
    My intension is to change this solution to a custom Widget that inherits from SelectMultiple.
    *** It still needs refactoring ***
"""

models.py

from django.db import models

MY_CHOICES = (
    (1, 'escolha1'),
    (2, 'escolha2'),
    (3, 'escolha3'),
    (4, 'escolha4'),
    (5, 'escolha5'),
)

class MeuConteudo(models.Model):
    meu_campo = models.CommaSeparatedIntegerField(
        blank=True, max_length=255,
    )

forms.py

from django import forms
from minhaapp.models import MeuConteudo, MY_CHOICES


class CommaSeparatedSelectInteger(forms.MultipleChoiceField):
    def to_python(self, value):
        if not value:
            return ''
        elif not isinstance(value, (list, tuple)):
            raise ValidationError(
                self.error_messages['invalid_list'], code='invalid_list'
            )
        return ','.join([str(val) for val in value])

    def validate(self, value):
        """
        Validates that the input is a string of integers separeted by comma.
        """
        if self.required and not value:
            raise ValidationError(
                self.error_messages['required'], code='required'
            )

        # Validate that each value in the value list is in self.choices.
        for val in value.split(','):
            if not self.valid_value(val):
                raise ValidationError(
                    self.error_messages['invalid_choice'],
                    code='invalid_choice',
                    params={'value': val},
                )

    def prepare_value(self, value):
        """ Convert the string of comma separated integers in list"""
        return value.split(',')


class MeuConteudoMultipleForm(forms.ModelForm):
    meu_campo = CommaSeparatedSelectInteger(
        choices=MY_CHOICES,
        widget=forms.SelectMultiple
    )

    class Meta:
        model = MeuConteudo

admin.py

from forms import MeuConteudoMultipleForm
from minhaapp.models import MeuConteudo
from minhaapp.forms import MeuConteudoMultipleForm


class MeuConteudoAdmin(admin.ModelAdmin):
    form = MeuConteudoMultipleForm


admin.site.register(MeuConteudo, MeuConteudoMultipleForm)

https://gist.github.com/romulocollopy/bffe38fa72af5bc427e1
like image 82
Rômulo Collopy Avatar answered Sep 20 '22 23:09

Rômulo Collopy