14

Assuming that I have such model

COLORS= (
    ('R', 'Red'),
    ('B', 'Yellow'),
    ('G', 'White'),
)
class Car(models.Model):
    name = models.CharField(max_length=20)
    color= models.CharField(max_length=1, choices=COLORS)

It displays as a selectbox in the admin panel however I would like my admin-user to multi select those colors like many-to-many relationship, how can this be achieved without a ('RB', 'Red&Blue'), type of logic

Serjik
  • 10,543
  • 8
  • 61
  • 70
Hellnar
  • 62,315
  • 79
  • 204
  • 279

6 Answers6

12

Can a Car have multiple colors? In that case color ought to be a many to many relationship rather than a CharField. If on the other hand you want to do something like Unix permissions (i.e. Red + Blue, Red + Blue + Green etc.) then assign numeric values of to each of them and make color an integer field.

Update

(After reading comment) You can use a custom form to edit your model in Admin instead of the default ModelForm. This custom form can use a multiple choice widget that lets users select multiple colors. You can then override the clean() method of the form to return a suitably concatenated value ('RB' etc.).

Update 2

Here is some code:

First, remove the choices from the model field. Also increase its maximum size to 2. We don't want choices here - if we do, then we'll have to add a choice for each combination of colors.

class Car(models.Model):
    ...
    color= models.CharField(max_length=2)

Second add a custom ModelForm to use in admin app. This form will override color and instead declare it as a multiple choice field. We do need choices here.

COLORS= (
    ('R', 'Red'),
    ('B', 'Yellow'),
    ('G', 'White'),
)

class CarAdminForm(ModelForm):
    color = forms.MultipleChoiceField(choices = COLORS)

    class Meta:
        model = Car

    def clean_color(self):
        color = self.cleaned_data['color']
        if not color:
            raise forms.ValidationError("...")

        if len(color) > 2:
            raise forms.ValidationError("...")

        color = ''.join(color)
        return color

Note that I have added only a couple of validations. You may want more and/or customize the validations.

Finally, register this form with admin. Inside your admin.py:

class CarAdmin(admin.ModelAdmin):
    form = CarAdminForm

admin.site.register(Car, CarAdmin)
Manoj Govindan
  • 72,339
  • 21
  • 134
  • 141
  • actually my example was bad, I have updated my COLORS for a better clarification. – Hellnar Aug 27 '10 at 08:53
  • @Hellnar: *if* your admin user selects multiple colors, how do you plan to store them? Or do you just want a multi select box even though you want to store only one color? – Manoj Govindan Aug 27 '10 at 08:59
7

I've constructed a complete working example with meaningful models. It works perfect. I've tested it on Python 3.4.x and Django 1.8.4. At first I run admin panel and create records for each option in Thema model

models.py

from django.db import models

class Author(models.Model):
    fname = models.CharField(max_length=50)
    lname = models.CharField(max_length=80)

    def __str__(self):
        return "{0} {1}".format(self.fname, self.lname)


class Thema(models.Model):
    THEME_CHOICES = (
        ('tech', 'Technical'),
        ('novel', 'Novel'),
        ('physco', 'Phsycological'),
    )
    name = models.CharField(max_length=20,choices=THEME_CHOICES, unique=True)

    def __str__(self):
        return self.name

class Book(models.Model):

    name = models.CharField(max_length=50)
    author = models.ForeignKey(Author)
    themes = models.ManyToManyField(Thema)

    def __str__(self):
        return "{0} by {1}".format(self.name,self.author)

forms.py

from django import forms

from .models import *

class BookForm(forms.ModelForm):
    themes = forms.ModelMultipleChoiceField(queryset=Thema.objects, widget=forms.CheckboxSelectMultiple(), required=False)

admin.py

from django.contrib import admin

from .models import *
from .forms import *

@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    pass


@admin.register(Book)    
class BookAdmin(admin.ModelAdmin):
    form = BookForm


@admin.register(Thema)
class ThemaAdmin(admin.ModelAdmin):
    pass
Serjik
  • 10,543
  • 8
  • 61
  • 70
  • Unlike the accepted answer (which, in Django 2.1/Python 3.7, gives you the error `django.core.exceptions.ImproperlyConfigured: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form CarAdminForm needs updating.`), this answer still works really well. +1 – ron_g Dec 07 '18 at 10:21
1

Use a separate table with colors (Red, Blue, Green), and, as you said, add a many to many relationship ? Choice type is not multiple choice, only a string with added UI and checkings.

Or, generate procedurally your choices with itertools.combinations, example:

choices = zip(
  [''.join(x) for x in itertools.combinations(['','B','R','G'],2)],
  [' '.join(x) for x in itertools.combinations(['','Blue','Red','Green'],2)],
)

 # now choices = [(' Blue', 'B'), (' Red', 'R'), (' Green', 'G'), ('Blue Red', 'BR'), ('Blue Green', 'BG'), ('Red Green', 'RG')]
makapuf
  • 1,370
  • 1
  • 13
  • 23
  • or, better, choices = sum([[(' '.join(x),''.join(i[0] for i in x)) for x in itertools.combinations(['Blue','Red','Green'],n)] for n in (1,2,3)],[]) – makapuf Aug 27 '10 at 09:54
1

For colors tuple, if you use integers instead of chars, you may use commaseparatedintegerfield for your model. But Do not forget, commaseparatedintegerfield is a database level structure,so your DBMS must support it.

Documentation link...

Mp0int
  • 18,172
  • 15
  • 83
  • 114
0

The easiest way I found (just I use eval() to convert string gotten from input to tuple to read again for form instance or other place)

This trick works very well

#model.py
class ClassName(models.Model):
    field_name = models.CharField(max_length=100)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.field_name:
            self.field_name= eval(self.field_name)



#form.py
CHOICES = [('pi', 'PI'), ('ci', 'CI')]

class ClassNameForm(forms.ModelForm):
    field_name = forms.MultipleChoiceField(choices=CHOICES)

    class Meta:
        model = ClassName
        fields = ['field_name',]

#view.py
def viewfunction(request, pk):
    ins = ClassName.objects.get(pk=pk)

    form = ClassNameForm(instance=ins)
    if request.method == 'POST':
        form = form (request.POST, instance=ins)
        if form.is_valid():
            form.save()
            ...
0

Django models come with JSONField() which you could use to serialize the list of integer values and later deserialize it into a list. List of integers are your colors.

https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.JSONField

Sher Sanginov
  • 431
  • 5
  • 9