Question

What I would like to do is to change the interface of an inline formset on the front end such that a checkbox can be associated with two different foreign key fields on a "through" model. Basically, I would like to collapse two dropdowns into one checkbox.

In models.py:

class Food(models.Model):
    name = models.CharField(max_length=10)        

class Day(models.Model):
    """Days of the week: Sunday, Monday, Tuesday, etc."""
    name = models.CharField(max_length=10)

class Meal(models.Model):
    """Breakfast, Lunch, Dinner"""
    name = models.CharField(max_length=10)

class Plan(models.Model):
    name = models.CharField()
    foods = models.ManyToManyField('Food',related_name='plans')
    days = models.ManyToManyField('Day',related_name='plans',through='Schedule')
    meals = models.ManyToManyField('Meal',related_name='plans',through='Schedule')

class Schedule(models.Model):
    plan = models.ForeignKey(Plan)
    day = models.ForeignKey(Day)
    meal = models.ForeignKey(Meal)

Each plan has one set of associated foods, and then is associated with certain meals on certain days through the Schedule model.

I want to have a model form where users can create a meal plan, where they say what foods they are going to eat on which days. For example, a user could select apple and banana first out of the set of foods, then they should be able to select which meals on which days they're going to eat an apple and a banana.

My view is:

def plan_add(request):
    form = forms.PlanForm();

    ScheduleFormSet = inlineformset_factory(Plan, Schedule)
    formset_schedule = ScheduleFormSet()

    response = render(request, 'template.html', {
        'form' : form,
        'formset_schedule' : formset_schedule,
    })
    return response

This gives me a form that will work, but an unfriendly user interface. For each schedule you want to add, you have to first select the day from a dropdown, then select the meal from a second dropdown. What I'd like to have is 7 table rows, and each row has a checkbox for each meal in it and you just check the boxes for the meals you want to put on the schedule. So, I want to make one form field (a checkbox) associated with two foreign key fields on the Schedule model: day and meal.

I'm wondering if there is a "Django" way to approach this, perhaps by overriding inlineformset_factory, or if this is such a weird thing to want to do that I should just build this form manually, validate it manually, and parse out and save the data manually.

Was it helpful?

Solution

Here's what I ended up doing. I didn't use inlineformset_factory, because I couldn't get the control I needed with inline formsets. For foods, I specified a custom ModelMultipleChoiceField with a CheckboxSelectMultiple widget, then did the same thing for each day by getting the days and looping through them.

class PlanForm(ModelForm):
    foods = forms.ModelMultipleChoiceField(
        queryset=Food.objects.all(), 
        required=False, 
        widget=forms.CheckboxSelectMultiple,
        label = "Foods:")

    def __init__(self, *args, **kwargs):
        super(PlanForm, self).__init__(*args, **kwargs)
        days = Day.objects.all()
        for day in days:        
            self.fields[day.name] = forms.ModelMultipleChoiceField(
                queryset=Meal.objects.all(), 
                required=False, 
                widget=forms.CheckboxSelectMultiple,
                label = day.name)

    class Meta:
        model = Plan
        fields = (['name','description','foods']+[day.name for day in Day.objects.all()])

This was the basic code to get the form that I wanted. Then, in your template, you could do something like this, and add your custom html:

{% for field in form %}
    {{ field.label_tag }}
    {% for choice in field %}
        {{choice}}
    {% endfor %}
{% endfor %}

I also ended up using this solution: http://schinckel.net/2013/06/14/django-fieldsets/ to get fieldsets, so I could do different rendering for each group on the form.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top