0

I am trying to use MultipleChoiceField from django Rest Framework fields as described in this link: https://pypi.org/project/django-multiselectfield/

A part of my code snippet is as below:

from rest_framework import fields
CHOICES = (
    ('publisher', 'Can Publish programs'),
    ('author', 'Can author programs')
)

class User(AbstractUser):
  email = EmailField(verbose_name=_('email address'), unique=True)
  edumap_roles = fields.MultipleChoiceField(choices=CHOICES, allow_blank=True)

But on admin console I see these fields like, instead of string items: enter image description here

But when I click on email_addres to see user details, I get following error: enter image description here

Edit: This is my UserAdmin class

@register(User)
class UserAdmin(CustomAdmin):
    """Define admin model for custom User model with no email field."""
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions', 'edumap_roles')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined',
                                           'created_at', 'updated_at')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    list_display = ('email', 'first_name', 'last_name', 'is_staff', 'edumap_roles')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)
    readonly_fields = ['created_at', 'updated_at']

My rest framework version is: 3.11.0. Am I missing to add anything?

2 Answers2

0

Of course, a "multiple choice field" doesn't exist in database land. Behind the scenes this is just like a CharField but with some additional serialization. This means that in your model definition we need to define it as such:

CHOICES = (
    ('publisher', 'Can Publish programs'),
    ('author', 'Can author programs')
)

class User(AbstractUser):
    email = EmailField(verbose_name=_('email address'), unique=True)
    edumap_roles = models.CharField(choices=CHOICES, null=True, blank=False)

However, Django does not yet know that this needs to be multiple choice. We need to create a Form for this. In a Form we can specify what kind of widget should be used for every field. E.g. we can specify that it should use a textarea instead of a normal text input. Your Form could look something like:

class UserForm(forms.ModelForm):
    edumap_roles = forms.MultipleChoiceField(widget=forms.SelectMultiple,
                                      choices=choices)
    class Meta:
        model = User
        fields = [
            'email',
            'edumap_roles',
            'password1',
            'password2',
            '...',
        ]
    

Note that we only need to specify the widgets for fields that we wish to overwrite. The other widgets are resolved automatically.

Then in your UserAdmin you can do:

@register(User)
class UserAdmin(CustomAdmin):
    ...
    form = UserForm

Forms and widgets are a large part of the 'admin' section, you can read more on them here: https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/ (there are different types of forms, but ModelForm seems appropriate here)
https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#module-django.forms.widgets

Snackoverflow
  • 736
  • 9
  • 31
  • In the link I mentioned, there is an alternate given for Django Rest Framework too (search for MultipleChoiceField). I tried using from multiselectfield import MultiSelectField, it works fine for me, but I am trying to avoid to install additional package as it migrates few other packages too that I do not have control upon, and got some other issue. If all fields are meant to be used only for forms then I will have to use django-multiselectfield only. – Pratibha Gupta Aug 11 '20 at 07:46
  • Then you need to create a custom form, I will update my answer – Snackoverflow Aug 11 '20 at 07:52
  • another issue I got with django-multiselectfield is, I can not migrate existing rows with default selection set to null, through auto-migration. Note: edumap_roles is the new column I am adding to an existing model. – Pratibha Gupta Aug 11 '20 at 07:54
  • @PratibhaGupta When adding a new column that is not nullable, you will always need to specify a default value. one thing you could do, is specify it like `(null=True, blank=False)`. This way a user can't leave the field empty, but the database does not require values per se. – Snackoverflow Aug 11 '20 at 08:11
  • I want to allow no selection by default. In that case, I am setting `blank=True`. As per my understanding I need to set either `null=True` or `blank=True`. Please correct me if I am wrong. – Pratibha Gupta Aug 11 '20 at 08:22
  • 1
    @PratibhaGupta If you wish to allow the user to leave the field empty, you need to set both `blank = True` (front-end validation) and `null = True` (database validation) – Snackoverflow Aug 11 '20 at 08:26
0

If you want to save states of your data in database you must use Django fields. You can use Django CharField instead of rest_framework fields to solve your problem. Instead of current field you can use something like following code:

from django.db import models


class User(AbstractUser):
    CHOICES = (
        ('publisher', 'Can Publish programs'),
        ('author', 'Can author programs')
    )
    email = models.EmailField(verbose_name=_('email address'), unique=True)
    edumap_roles = models.CharField(choices=CHOICES)

Note: Do not forget to run manage.py makemigrations and manage.py migrate to apply your new changes to your database. In case of performance you can also change the edumap_roles to something like:

class User(AbstractUser):
    CHOICES = (
        (1, 'Can Publish programs'),
        (2, 'Can author programs')
    )
    email = models.EmailField(verbose_name=_('email address'), unique=True)
    edumap_roles = models.PositiveSmallIntegerField(choices=CHOICES)
Roham
  • 1,970
  • 2
  • 6
  • 16
  • edumap_roles = models.CharField(choices=CHOICES) allows selecting only one value out of all choices. My requirement is to select multiple values. – Pratibha Gupta Aug 11 '20 at 07:50