0

In the Django admin panel, all fields are saved to the database, except for the flags field of the SubscriberPlan model. That is, I can (un)check any flag and try to thus update a record, but the flag statuses won't be saved to the database.

If I run python manage.py shell, import SubscriberPlan, do something like

   plan = SubscriberPlan.objects.all()[0],
   plan.flags = "a"
   plan.save()

then the database will be updated and the Active flag will be displayed in the Admin panel, but, still, it won't be possible to update it from the Admin panel.

So, how is it possible in Django to save this kind of a field to the database from the Admin panel? To be honest, I don't understand why it's not saved by default, while other fields are saved. It seems that the Admin panel, for some reason, doesn't pass the checkmark values in its form.

admin.py

from django.contrib import admin
from django.utils.safestring import mark_safe


class SubscriberPlanFlagsWidget(forms.Widget):
    available_flags = (
        ('a', ('Active')),
        ('n', ('New')),
        ('p', ('Popular')),

    def render(self, name, value, attrs=None, renderer=None):
        html = []
        for f in self.available_flags:
            html.append('<li><input type="checkbox" id="flag_%(key)s" %(checked)s key="%(key)s"/><label for="flag_%(key)s">%(name)s</label></li>' % {
                'key': f[0], 'name': f[1], 'checked': 'checked' if f[0] in value.lower() else ''})
        html = '<input type="hidden" name="%s" value="%s"/><ul class="checkbox flags">%s</ul>' % (name, value, ''.join(html))
        return mark_safe(html)


class SubscriberPlanAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == 'flags':
            kwargs['widget'] = SubscriberPlanFlagsWidget
        return super(SubscriberPlanAdmin, self).formfield_for_dbfield(db_field, **kwargs)

models.py

from django.db import models


class SubscriberPlan(models.Model):
    name = models.CharField(max_length=50, verbose_name=("Name"))
    price = models.DecimalField(max_digits=15, decimal_places=2,
                                verbose_name=("Price"))
    flags = models.CharField(max_length=30, verbose_name=("Flags"),
                             default='', blank=True)

    def _check_flag(self, name):
        return name in self.flags.lower()

    def active(self):
        return self._check_flag('a')

    def new(self):
        return self._check_flag('n')

    def popular(self):
        return self._check_flag('p')

1 Answer 1

1

Your widget's render method translates the flags field into checkboxes on the form, but when the form is submitted you need to go the other direction, and translate the checkboxes back into flags. The widget doesn't know how to do that automatically. From looking at the django source code and the docs, you need to override the value_from_datadict method. Give this a try:

class SubscriberPlanFlagsWidget(forms.Widget):

    ...
    
    def value_from_datadict(self, data, files, name):
        value = ''
        for f in self.available_flags:
            if f[1] in data:
                value += f[0]
        return value
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you for your insight. When I print(data) in value_from_datadict, there is no data from the rendered HTML, it looks like this: <QueryDict: {'csrfmiddlewaretoken': ['QHAvWUtWxyhNpp3wQFJTH7tFOtoCF6E5LApyleXU0ACuY1wDyo9Q7zVEjLUI9dxB'], 'name': ['Basic Plan'], 'price': ['5.00'], 'flags': [''], '_continue': ['Save and continue editing']}> Do you have any idea why there is no data from the rendered HTML?
Only checked boxes will be listed in post data. Are any of the boxes checked?
Look at this question and see if it helps.
I've changed the code to ``` class SubscriberPlanAdmin(admin.ModelAdmin): labels = {'flags': 'label for flags'} def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name == 'flags': kwargs['widget'] = forms.CheckboxSelectMultiple(choices=available_flags) # SubscriberPlanFlagsWidget return super(SubscriberPlanAdmin, self).formfield_for_dbfield(db_field, **kwargs) ``` The flags are now saved, but in the form ['a', 'b', 'c'] instead of abc. Either way, the good news they are now saved, but are not read.
My guess is that I'll need to change the model flags field type to models.ManyToManyField for this to work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.