0

Most of the questions on this seem to be focused on saving down m2m tables. I'm curious about how to view fields from the intermediary table in one (or two combined) forms.

The idea is I have many resources and many meals. Different meals can have the same resource, but in differing amounts. Say: Burrito has x lbs of beef, but fajitas have y.

I want to create a form to edit the Burrito meal so that I can see/edit/create: The name of the meal, the resources in the meal, and the amount associated with the resource in that meal. My current code displays a form with all but the amounts associated with the resources via the m2m table

I have two models connected through an intermediary table:

class Resource(models.Model):
    name = models.CharField(max_length=200)
    unit = models.ForeignKey(Unit)
    units_per_pack = models.PositiveSmallIntegerField()
    packs_per_case = models.PositiveSmallIntegerField()
    allergens = models.ManyToManyField(Allergen, blank=True)

class Meal(models.Model):
    name = models.CharField(max_length=200)
    resources=models.ManyToManyField(Resource,through='MealResourceRelationship')
    recipe = models.TextField(default='')

class MealResourceRelationship(models.Model):
    resource = models.ForeignKey(Resource)
    meal = models.ForeignKey(Meal)
    units_per_person = models.DecimalField(max_digits=19,decimal_places=2)

I'm trying to use a simple ModelForm as seen below:

class MealForm(forms.ModelForm):
    class Meta:
    model = Meal
    fields = '__all__'

with this view:

def meal_edit(request, pk=None, template_name='foodstuffs/meal_edit.html'):
    if id:
        meal = get_object_or_404(Meal, pk=pk)
    else:
        meal = Meal()
    if request.POST:
       form = MealForm(request.POST,instance=meal)
       if form.is_valid():
            meal_mod = form.save(commit=False)
            meal_mod.save()

            # remove existing resources                                                                                                                                                                      
            meal_mod.resources.clear()
            for resource in form.cleaned_data.get('resources'):
                meal_resource_rel = MealResourceRelationship(meal=meal_mod,
                                                             resource=resource,
                                                             units_per_person=1)
                meal_resource_rel.save()
            # messages.add_message(request, messages.SUCCESS, _('Meal correctly saved.'))                                                                                                                    
            # If the save was successful, redirect to another page                                                                                                                                           
            redirect_url = reverse('meal_list')
            return HttpResponseRedirect(redirect_url)
    else:
        form = MealForm(instance=meal)

    args = {}
    args.update(csrf(request))
    args['form'] = form
    args['meal'] = meal
    return render_to_response(template_name, args)

Any Ideas? Thank you!

Louis Barranqueiro
  • 10,058
  • 6
  • 42
  • 52
g.meels
  • 1
  • 2

1 Answers1

0

Based on the solution that Django offers (short of completely custom forms), I think it's easiest to look at this problem in two parts:

  1. How to provide a form for all of the MealResourceRelationships that are related to a given Meal
  2. How to show the form from number 1 alongside the form for the Meal itself

The answer to number 1 lies in Formsets, which allow multiple instances of a form to be rendered together. For your specific case, where you want these instances to be tied to a parent object (a meal), you are interested in Inline Formsets, which allow you to render multiple instances of a form related by a foreign key. See this post to see how to use inline formsets w/ m2m through relationships.

For part 2) you just need to render both the original form and the inline formset to the same page. See this post for an example.

Based on this you can update your view as follows:

def meal_edit(request, pk=None, template_name='foodstuffs/meal_edit.html'):
    # ADDED: Created an inline formset factory for the meal resource relationship
    MealResourceFormset = forms.inlineformset_factory(Meal, Meal.resources.through, exclude=[])

    if id:
        meal = get_object_or_404(Meal, pk=pk)
    else:
        meal = Meal()

    if request.POST:
       form = MealForm(request.POST, instance=meal)
       if form.is_valid():
            meal_mod = form.save(commit=False)
            meal_mod.save()

            # remove existing resources                                                              
            meal_mod.resources.clear()
            for resource in form.cleaned_data.get('resources'):
                meal_resource_rel = MealResourceRelationship(meal=meal_mod,
                                                         resource=resource,
                                                         units_per_person=1)
                meal_resource_rel.save()
            # messages.add_message(request, messages.SUCCESS, _('Meal correctly saved.'))            
            # If the save was successful, redirect to another page                                   
            redirect_url = reverse('meal_list')
            return HttpResponseRedirect(redirect_url)
    else:
        # ADDED/EDITED: create both the meal form and resource formsets
        meal_form = MealForm(instance=meal)
        resource_formset = MealResourceFormset(instance=meal)                                                         

    args = {}
    args.update(csrf(request))
    args['form'] = meal_form
    # ADDED: pass through resource_formset to template
    args['formset'] = resource_formset
    args['meal'] = meal
    return render_to_response(template_name, args)

You will also need to update your template to render the formset, and update MealForm to not render the resources field (since you are rendering it via the formset). You can further customize each part of this by editing the forms for each.

Hope this helps as a starter and good luck w/ your project!

Community
  • 1
  • 1
msheets
  • 396
  • 2
  • 4