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>
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With