views:

307

answers:

2

I have two models that looks like this:

class RouteBase(models.Model):
 base  = models.ForeignKey("Base")
 route  = models.ForeignKey("Route")
 sequence = models.IntegerField()

class Route(models.Model):
 bases  = models.ManyToManyField("Base", through="RouteBase", blank=True)
 description = models.TextField(blank=True)
 #and a few other attributes omitted for brevity

...then a modelform that looks like this:

class RouteBaseForm(ModelForm):
 base = forms.ModelChoiceField(queryset=Base.objects.all(), widget=forms.TextInput)
 sequence = forms.IntegerField(widget=forms.HiddenInput)

 class Meta:
  model = RouteBase

As you can see, the sequence widget is hidden, as I want this field to be automatically handled by django. I want the user to have to just enter the Base via a text box. The sequence is inferred by the order of the textboxes.

I have created a formset with this form for creating/editing all the bases in the route:

RouteBaseFormset = inlineformset_factory(Route, RouteBase, form=RouteBaseForm, extra=5, )

When this formset is created, the sequence field is empty. I need to fill it in with values before I save the formset (else it won't validate). I can think of about 4 ways to go about this

  1. Right before I send the formset off to the template, I run this code:
    i=1
    for form in formset.forms:
        form.fields["sequence"].initial = i
        i += 1

This works fine, except for one problem. When the formset gets submitted back to the view for saving, all 5 extra fields that have been created with the formset are filled in with a sequence value. This causes problems when the user only wants to add 2 or 3 bases to the route. Validation errors popup because the required field "base" is empty for that form. I could run a bit of code after the formset has been POSTed that checks to see if a base is present, if not, remove the sequence, but if I'm going to do that, I may as well...

  1. Run a bit of code when the formset is POSTed that checks to see if a Base has been entered, if so, add a sequence, if not, then leave that field blank. That way when you try to .save() the formset, the empty values ensure that that particular form is not put into the database. The only problem is that I can't do anything to the form until I run .save(commit=False) which I can't do because the formset doesn't validate. But I could...

  2. Add the sequence values by copying the request.POST variable and manually setting the sequence, but that seems awfully hacky.

  3. I could also just remove blank=True from one of my RouteBase fields, but I don't really want to do that.

So what should I do here?

+2  A: 

If you never intend to have a user edit the sequence field and plan on always calculating it's value on the back end, you can exclude it from the form, rather than having it hidden.

Then you can use commit=False and handle the calculation of your sequence field as needed.

Harold
That doesn't seem to be working. It doesn't matter what fields I exclude, if there is no sequence, the form won't validate, and therefore won't `.save(commit=False)`
nbv4
You'll need to set blank=True in your model for that field. Since you are responsible for generating that value yourself (not the user) you don't need form validation for it.
Harold
A: 
newPOST = request.POST.copy()
i=1
for index in range(0, int(request.POST["routebase_set-TOTAL_FORMS"])-1):
 if request.POST["routebase_set-" + str(index) + "-base"]:
  newPOST["routebase_set-" + str(index) + "-sequence"] = i
  i += 1
 else:
  newPOST["routebase_set-" + str(index) + "-sequence"] = ""

Honestly, this seems to work better than any messing around with form validation. It's not as hacky as I thought it be either...

nbv4
-1 this is quite ugly. The form validation approach is much simpler and cleaner.
Carl Meyer