Django BooleanField as radio button but between inline forms

749 Views Asked by At

In my project, users can have any number of 'contact ways' with them. For example one user can have only mobile defined and the other can have mobile, mail, Skype and some other. There should be possibility to set for every user only one main 'way of contact', so I created model like below:

 class Account(AbstractNamedUser):
       ....

 class Contact(models.Model):
       account = models.ForeignKey(Account)
       value = models.CharField(max_length=100)
       is_default = models.BooleanField(default=False)

In admin backend I'm using admin.TabularInline for it, but unfortunately is_default fields are independent checkboxes between each inline form. What I'd like to have is to change them into radio buttons and of course only one 'row' can be default. I've tried formfield_overrides, some ModelAdmin.form or widgets = {'is_default':forms.RadioSelect } but they work correctly only for choice fields in single form.

Is there any possibility of indicating in Django that field is_default should be treated as radio button between inline forms?

2

There are 2 best solutions below

0
On

To have only one "is_default" checkbox selected, you can use Javascript (here with jQuery) in the template where the inline form is rendered.

Use a for loop to add a "change" event handler to each checkbox. The event handler, when you put a check mark, will loop and uncheck all the other checkboxes in the inline form:

<script>
$( document ).ready(function() {
    var num_forms = $('#id_form-TOTAL_FORMS').val();
    for (n = 0; n < num_forms; n++) {
        (function(n) {
            $('#id_form-' + n + '-is_default').on('change', function() {
                if (this.checked) {
                    // If you can add "rows" to the formset, num_forms can change
                    var num_forms = $('#id_form-TOTAL_FORMS').val();
                    // this.id property does not start with "#"
                    var changed_control_name = '#' + this.id
                    // Uncheck all the other controls
                    for (i = 0; i < num_forms; i++) {
                        var control_name = '#id_form-' + i + '-is_default'
                        if (control_name != changed_control_name) {
                            $(control_name).prop("checked", false);
                        }
                    }
                } else {
                    // Prevent "uncheck" the already checked control.
                    // If you want to allow "no default", remove this "else" clause.
                    this.checked = true;
                }
            });
        })(n);
        {% endif %}
    }
})
</script>

This code assumes that the formset uses the default prefix "form"; see https://docs.djangoproject.com/en/3.2/topics/forms/formsets/#customizing-a-formset-s-prefix. Modifying this code to use another prefix is left as exercise to the reader ;-)

Note: If adding event handlers this way looks more complicated than it should be, have a look to this StackOverflow question. This code is inspired by Rory McCrossan's answer to that question.

1
On

Yes, I personally use it so often.

Add class attribut to your ModelForm default field:

    def __init__(self, *args, **kwargs):
      super(FormClassName, self).__init__(*args, **kwargs)
      self.fields['default'].widget.attrs['class'] = 'default-img'

Use JS or Jquery to manipulate:

    $(document).on("click", '.ElementClass', function () {
    $('.ElementClass').prop('checked', false);
    $(this).prop('checked', true);
});