1

I am trying to create a CheckboxSelectMultiple widget that lists all the content types of my project. I started by using the base widget when defining a MultipleChoiceField field in a ModelForm and it worked fine. I would like now to make it a custom widget I can import though an app into any project.

Here is the code I am using:

# myapp/models.py

from django.db import models

class Tiger(models.Model):
    color = models.CharField(max_length=100)

# myapp/admin.py

from django import forms
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from myapp.models import Tiger

class TigerForm(forms.ModelForm):
    """
    Default ModelForm that will be overriden.
    """
    class Meta:
        model = Tiger

This is where I define my custom widget. I am guessing I am not passing the list of values correctly (see comment in the code).

class TigerWidget(forms.CheckboxSelectMultiple):
    """
    Custom widget that displays a list of checkboxes for each content type.
    The goal is to make it generic enough to put it in an external app
    that can be imported into any project.
    """
    def __init__(self, attrs=None, choices=()):

        # List all content types
        content_types = ContentType.objects.all()
        classes = tuple([(c.model, c.name) for c in content_types])

        # Define widget choices as the list of these content types
        self.choices = classes  # I am guessing it should be done differently?

        # Select all of them by default
        self.initial = [c[0] for c in classes]

        # Same behavior as parent
        super(TigerWidget, self).__init__(attrs)

Here are the remaining classes that make use of it.

class TigerCustomForm(TigerForm):
    """
    Custom form that adds a field using the custom widget to the form.
    """
    # content_types = ContentType.objects.all()
    # classes = tuple([(c.model, c.name) for c in content_types])

    # This works fine.
    # nickname = forms.MultipleChoiceField(
    #     widget=forms.CheckboxSelectMultiple,
    #     choices=classes,
    #     # Select all by default
    #     initial=[c[0] for c in classes]
    # )

    # This does not. An empty list (<ul></ul>) is rendered in the place of the widget.
    nickname = forms.MultipleChoiceField(
        widget=TigerWidget,
    )

class TigerAdmin(admin.ModelAdmin):
    form = TigerCustomForm

admin.site.register(Tiger, TigerAdmin)
admin.site.register(ContentType)

Thanks in advance for your help.

Dr Tuple
  • 23
  • 1
  • 4

2 Answers2

1

A widget is responsible for rendering the html e.g. display a multiple select box (forms.MultipleSelect) or multiple checkboxes (forms.CheckboxSelectMultiple). This is a separate decision from the choices to display for the field.

I think it would be better to subclass forms.MultipleChoiceField and set the choices there.

class TigerMultipleChoiceField(forms.MultipleChoiceField):
    """
    Custom widget that displays a list of checkboxes for each content type.
    The goal is to make it generic enough to put it in an external app
    that can be imported into any project.
    """
    def __init__(self, *args, **kwargs):
        # Same behavior as parent
        super(TigerMultipleChoiceField, self).__init__(*args, **kwargs)


        # List all content types
        content_types = ContentType.objects.all()
        classes = tuple([(c.model, c.name) for c in content_types])

        # Define widget choices as the list of these content types
        self.choices = classes

        # Select all of them by default
        self.initial = [c[0] for c in classes]
Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • Thanks @Alasdair, but I already tried it. It does not solve the issue. – Dr Tuple Oct 27 '11 at 07:56
  • I replaced my answer with a different approach. – Alasdair Oct 27 '11 at 12:54
  • @Alasdair while googling i found your answer here,i am also struggling with a problem need some idea to perform this please see my SO question http://stackoverflow.com/questions/18592136/add-image-to-all-check-box-django – Monk L Sep 07 '13 at 10:37
  • @MonkL this question is a bit different to yours, because it is about customising the values of the options displayed, and yours is about customising the presentation of the options in the template (by adding an image to each one). You should be able to add the image using CSS, as Professor Dante suggests in his answer. I'm afraid I don't have time to go into your question in detail. I hope you manage to solve your problem. – Alasdair Sep 07 '13 at 11:27
  • @Alasdair your are correct,what i currently required is Multiplechoicefield render values in
  • tag,i want to replace that
  • to a
    or tag.If you have any idea please share with me.If i apply css it is render one time because choices are list of
  • tag.Thanks
– Monk L Sep 07 '13 at 11:33
  • @MonkL as I said, I can't offer you detailed help. Hope you manage to figure it out. – Alasdair Sep 07 '13 at 11:51