Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Initializing a FormSet of custom forms with instances

Given the following models:

class Graph(models.Model):     owner = models.ForeignKey(User)      def __unicode__(self):         return u'%d' % self.id  class Point(models.Model):     graph = models.ForeignKey(Graph)      date  = models.DateField(primary_key = True)     abs   = models.FloatField(null = True)     avg   = models.FloatField(null = True)      def __unicode__(self):         return u'%s' % self.date 

I am trying to create a form for editing lists of Points. The HTML input tags require additional attributes to be set, so I am using the following custom form:

class PointForm(forms.ModelForm):     graph = forms.ModelChoiceField(queryset = Graph.objects.all(),                                    widget   = forms.HiddenInput())     date  = forms.DateField(widget = forms.HiddenInput(), label = 'date')     abs   = forms.FloatField(widget = forms.TextInput(                                       attrs = {'class': 'abs-field'}),                             required = False)      class Meta:         model  = Point         fields = ('graph', 'date', 'abs')  # Other fields are not edited.      def pretty_date(self):         return self.data.strftime('%B') 

At this point I do not know how to pass instances of the Point class to a FormSet:

def edit(request):     PointFormSet = forms.formsets.formset_factory(PointForm, extra = 0)     if request.method == 'POST':         return      # Receive 3 points to edit from the database.     graph, res = Graph.objects.get_or_create(id = 1)     one_day    = datetime.timedelta(days = 1)     today      = datetime.date.today()     do_edit    = []     for date in [today - (x * one_day) for x in range(3)]:         point, res = Point.objects.get_or_create(graph = graph, date = date)         do_edit.append(point)      formset = PointFormSet(????) # How is this initialized with the points? 

I found a hack that somewhat works, but it leads to errors later on when trying to process the resulting POST data:

do_edit = [] for date in [today - (x * one_day) for x in range(3)]:     point, res    = Point.objects.get_or_create(graph = graph, date = date)     data          = point.__dict__.copy()     data['graph'] = graph     do_edit.append(data)  formset = PointFormSet(initial = do_edit) 

How is this done correctly?

For the reference, my template looks like this:

<form action="" method="post"> {{ formset.management_form }} <table>     <tbody>     {% for form in formset.forms %}         <tr>             <td>{{ form.graph }} {{ form.date }} {{ form.pretty_date }}:</td>             <td width="100%">{{ form.abs }}</td>         </tr>     {% endfor %}     </tbody> </table> </form> 
like image 418
knipknap Avatar asked Jan 02 '10 17:01

knipknap


People also ask

What is inline formset in django?

Django formset allows you to edit a collection of the same forms on the same page. It basically allows you to bulk edit a collection of objects at the same time.

What is instance in form django?

To facilitate this process, Django adds the instance field to the form reference, containing the form data structured as an instance of the underlying model of the model form. The instance field is particularly important when you need or must manipulate the model data prior to attempting a model operation.


1 Answers

The trick is to use a "ModelFormset" instead of just a formset since they allow initialization with a queryset. The docs are here, what you do is provide a form=* when creating the model formset and queryset=* when your instantiating the formset. The form=* arguement is not well documented (had to dig around in the code a little to make sure it is actually there).

def edit(request):     PointFormSet = modelformset_factory(Point, form = PointForm)     qset = Point.objects.all() #or however your getting your Points to modify     formset = PointFormset(queryset = qset)     if request.method == 'POST':         #deal with posting the data         formset = PointFormset(request.POST)         if formset.is_valid():             #if it is not valid then the "errors" will fall through and be returned             formset.save()         return #to your redirect      context_dict = {'formset':formset,                     #other context info                     }      return render_to_response('your_template.html', context_dict) 

So the code walks through easily. If the request is a GET then the instantiated form is returned to the user. If the request is a POST and the form is not .is_valid() then the errors "fall through" and are returned in the same template. If the request is a POST and the data is valid then the formset is saved.

Hope that helps.

-Will

like image 107
JudoWill Avatar answered Sep 25 '22 16:09

JudoWill