views:

566

answers:

2

I am trying to use inlineformset_factory to generate a formset. My models are defined as:

class Measurement(models.Model):
    subject = models.ForeignKey(Subject)
    experiment = models.ForeignKey(Experiment)
    assay = models.ForeignKey(Assay)
    values = models.CommaSeparatedIntegerField(blank=True, null=True)

class Experiment(models.Model):
    date = models.DateField()
    notes = models.TextField(max_length = 500, blank=True)
    subjects= models.ManyToManyField(Subject)

in my view i have:

def add_measurement(request, experiment_id):
    experiment = get_object_or_404(Experiment, pk=experiment_id)
    MeasurementFormSet = inlineformset_factory(Experiment, Measurement, extra=10, exclude=('experiment'))
    if request.method == 'POST':
        formset = MeasurementFormSet(request.POST,instance=experiment)
        if formset.is_valid():
            formset.save()
            return HttpResponseRedirect( experiment.get_absolute_url() ) 
    else:
        formset = MeasurementFormSet(instance=experiment)
    return render_to_response("data_entry_form.html", {"formset": formset, "experiment": experiment }, context_instance=RequestContext(request))

but i want to restrict the Measurement.subject field to only subjects defined in the Experiment.subjects queryset. I have tried a couple of different ways of doing this but I am a little unsure what the best way to accomplish this is. I tried to over-ride the BaseInlineFormset class with a new queryset, but couldnt figure out how to correctly pass the experiment parameter.

Updated answer (I also included the information from here as a way to pass the parameter to the formset link):

views.py

def add_measurement(request, experiment_id):    
    experiment = get_object_or_404(Experiment, pk=experiment_id)    
    MeasurementFormSet = inlineformset_factory(Experiment, Measurement, extra=10, can_delete=True, form=MeasurementForm)    
    MeasurementFormSet.form = staticmethod(curry(MeasurementForm, experiment=experiment))
    if request.method == 'POST':
        formset = MeasurementFormSet(request.POST)      
        if formset.is_valid():
        formset.save()
        return HttpResponseRedirect( experiment.get_absolute_url() )    
    else:
        formset = MeasurementFormSet()
        return render_to_response("data_entry_form.html", {"formset": formset, "experiment": experiment }, context_instance=RequestContext(request))

forms.py

class MeasurementForm(ModelForm):
    class Meta:
        model = Measurement
    def __init__(self, *args, **kwargs):
        experiment = kwargs.pop('experiment')
        super(MeasurementForm, self).__init__(*args, **kwargs)
        self.fields["subject"].queryset = Subject.objects.filter(experiment=experiment)
+1  A: 

(edit: did not read code blocks properly, here should be a solution to your problem):

I believe you need: http://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield

Forms.py:

class MeasurementForm(ModelForm):
 subject = forms.ModelChoiceField(queryset = Expirement.objects.all())
 class Meta:
   model = Measurement

Views.py:

inlineformset_factory(
  Experiment, Measurement, extra=10, 
  exclude=('experiment'), form=MeasurementForm
  )

Binding to formset is done using form parameter.

h-kippo
I tried this but not much luck. What is the purpose of the filter and prefix elements?
Dave
Edited my answer yesterday, modelchoicefield should have the ingredients for a solution.
h-kippo
I altered my code (see the addition in the question) but now the error is upon saving. The error is now "Cannot assign "<Measurement: Measurement object>": "Measurement.experiment" must be a "Experiment" instance."
Dave
Sorry, can't help. Updated code in your question is not using ModelChoiceField approach. I might have not understood the root cause of your problem as my proposal was not useful for you.
h-kippo
A: 

Hi there. I was through the same issue (initialize inlineforms with a limited possible values), and the Updated answer works great. Thanks for that. Anyway, there is something that could be done better I think, but I have no clue how to make it done. The new issue in that solution is that you hit the database in each inlineform: instead of using the same queryset in all the same fields, recalculates it each time in this line:

 self.fields["subject"].queryset = Subject.objects.filter(experiment=experiment)

Am I right in this issue or there is some lazy-django-magic behind the hood? If I'm right, how could I avoid the (possible hundred) of hits to the DB? Greetings, Pedro

pedromagnus