modelformset question - interspersing extra forms among existing forms

70 views
Skip to first unread message

Richard Brockie

unread,
Dec 29, 2014, 6:07:19 PM12/29/14
to django...@googlegroups.com
Hi,

I have the following pseudo model that I will use as the basis of a modelformset:

class Entry:
    athlete = ForeignKey(athlete)
    category = ForeignKey(category)
    race_number = IntegerField

I have a page that displays each athlete's details so the choice of athlete has already been made. I want to add a formset that allows a user to specify a race_number for each of the possible categories available.

An obvious way would be for there to be one Entry for each athlete/category, but in practise this will result in a large entry table that is only ~10% actual data, with the rest being superfluous rows. I'd prefer to avoid this.

Let's assume that an athlete is already entered in 2 categories out of a possible 10. The queryset will return the 2 existing entries, and I can use extra=8 to add in the other possibilities. However, I am not yet done. I want to do the following and would like advice on how to accomplish this:
  1. The category for each the 8 possible new entries needs to be assigned before displaying the form - I already know what they are and want to display these categories to the user, leaving the race_number as the only input field.
  2. The categories have a particular (chronological) order that I want respected in displaying the forms to the user, so the 2 existing and 8 possible new categories need to be interspersed to follow the correct order.
  3. When it comes time to save the formset, I want only those categories that have been changed by having a race_number assigned or removed to be saved. I want the entries where the race_number has been removed to remain in the entry table. I think I need a way to detect and remove the forms that have not been changed by the user before saving the formset.

Any pointers as to how to do this?

Thanks!
R.

Collin Anderson

unread,
Jan 1, 2015, 6:31:45 PM1/1/15
to django...@googlegroups.com
Hi,

Your case is complicated enough that I'd actually recommend not using formsets here and processing a list of plain ModelForms yourself in the view. I bet formsets will ending out getting in your way.

Here's a some messy code:

class EntryForm(forms.ModelForm):
    race_number
= Entry._meta.get_field('race_number').formfield(required=False)
   
class Meta:
        model
= Entry
        fields
= ['race_number']

def the_view(request, athlete_id):
    athlete
= get_object_or_404(Athlete, id=athlete_id)
    existing
= {e.category_id: e for e in athlete.entry_set.all()}
    entries
= []
   
for category in Category.objects.order_by('particular'):
        entries
.append(existing.get(category.pk, Entry(athlete=athlete, category=category)))
   
if request.method = 'POST':
        forms
= [EntryForm(request.POST, instance=e, prefix=e.category.pk) for e in entries]
       
if all([f.is_valid() for f in forms]):  # be sure to call is_valid() on every form
           
for entry in entries:
               
if entry.race_number:
                    entry
.save()
               
if entry.pk and not entry.race_number:
                    entry
.delete()
   
else:
        forms
= [EntryForm(instance=e, prefix=e.category.pk) for e in entries]
   
return render(request, 'template.html', {'athlete': athlete, 'forms': forms})

{% for form in forms %}
Category: {{ form.instance.category }}
{{ form }}
{% endfor %}

Collin

Richard Brockie

unread,
Jan 2, 2015, 9:42:47 PM1/2/15
to django...@googlegroups.com
Hi Collin,

Thanks very much for the advice. I'll be getting to this part of my development in the next few days and will report back when I have things working.

One question in the meantime - what does this line do given that my model will already explicitly not require the race_number?

race_number = Entry._meta.get_field('race_number').formfield(required=False)


Wishing everyone a prosperous New Year.
R.

Collin Anderson

unread,
Jan 4, 2015, 2:41:36 PM1/4/15
to django...@googlegroups.com
Hi,

If the field on the model is already blank=True, then you don't need that.

Also, I realized my (completely untested :) code doesn't exactly match the behavior you want, but I hope it's a good enough start. You may need to store a reference to the original race_number to decide if you need to .save() or not.

Collin

Richard Brockie

unread,
Jan 4, 2015, 11:09:38 PM1/4/15
to django...@googlegroups.com
Hi Collin,

Yes, you've answered my main question which was how to go about the implementation. I'll need to go through the details anyway, but you have given me a good start.

Thanks again,
R.

Richard Brockie

unread,
Jan 7, 2015, 10:34:34 AM1/7/15
to django...@googlegroups.com
Hi Collin,

Thanks again for the suggestion - I have the bare-bones version working in my code. Now to add in race_number validation and pretty up the form...

R.

Richard Brockie

unread,
Jan 12, 2015, 10:58:46 AM1/12/15
to django...@googlegroups.com
Hello again Collin,

I am using your example as the basis for another similar set of forms and I spotted something subtle that passed me by the first time through.

Namely, that to save the data, you are calling the model.save() method, rather than the form.save() method that is generally shown in the documentation examples.

I've tried both, and they both work. From the documentation I was expecting that this line:


    forms = [EntryForm(request.POST, instance=e, prefix=e.category.pk) for e in entries]

would be one-way, in that the POST and instances would combine in the forms and form.save() would be the method that should be used. It appears however that the POST data also end up in the instances.

I wasn't expecting that, but it appears to work. Is there a preferred or standard method that I should think about using?

R.

Collin Anderson

unread,
Jan 14, 2015, 8:51:31 AM1/14/15
to django...@googlegroups.com
Hi Richard,

Yes, my way is pretty hacky. The proper way is to get the instance from the form.save() method like you mentioned.

Collin
Reply all
Reply to author
Forward
0 new messages