The Maestro
The Maestro

Reputation: 689

Showing form within another one

I want to show a form within another form.

For example I have these models:

class Measure(models.Model):

    length = models.ForeignKey(Statistics, related_name='Length', null=True, blank=True)
    surface_area = models.ForeignKey(Statistics, related_name='Surface area+', null=True, blank=True)
    section_area = models.ForeignKey(Statistics, related_name='Section area+', null=True, blank=True)
    volume = models.ForeignKey(Statistics, related_name='Volume', null=True, blank=True)
    diameter = models.ForeignKey(Statistics, related_name='Diameter', null=True, blank=True)


class Statistics(models.Model):
    total = models.FloatField('Total', null=True, blank=True)
    avg = models.FloatField('Average', null=True, blank=True)
    min = models.FloatField('Minimum', null=True, blank=True)
    max = models.FloatField('Maximum', null=True, blank=True)
    standard_deviation = models.FloatField('Standard deviation', null=True, blank=True)

and then I have these forms corresponding to the previous models:

class StatisticsForm(forms.ModelForm):
    class Meta:
        model = Statistics
        fields = '__all__'

class MeasureForm(forms.ModelForm):
    class Meta:
        model = Measure
        fields = '__all__'

        # Here I have to say that the right form for each field is the StatisticForm

The ForeignKey in the forms is rendered as a combo box includes all the objects in the other table (in my case the Statistics table), I want to replace the combo box with an object of StatisticsForm so I can control the way I render the Statistics objects

Thank you very much.

Upvotes: 0

Views: 48

Answers (1)

Sebastian Wozny
Sebastian Wozny

Reputation: 17526

Your database scheme and models are incorrectly designed to solve the problem at hand. You are defining a "has many" relationship in the wrong direction. One Measurement is supposed to have several Statistics, however one Statistics is not supposed to have many Measurement.

As your models are set up right now the ForeignKey is on the wrong side of the relationship. You should do this instead:

class Measure(models.Model):
    def save(self,*args,**kwargs):
        result = super(Measure, self).save(*args,**kwargs)
        Statistics.objects.create(name='length', measurement=self)
        Statistics.objects.create(name='section', measurement=self)
        Statistics.objects.create(name='surface', measurement=self)
        Statistics.objects.create(name='volume', measurement=self)
        Statistics.objects.create(name='diameter', measurement=self)
        return result

To provide the same comfort in accessing the Statisticss for one Measurement as in your current code you can add a couple of @property shortcuts:

class Measure(models.Model):
    @property
    def length(self):
        return self.statistics_set.get(name='length')
    @property
    def section(self):
        return self.statistics_set.get(name='section')
    @property
    def surface(self):
        return self.statistics_set.get(name='surface')
    @property
    def volume(self):
        return self.statistics_set.get(name='volume')
    @property
    def diameter(self):
        return self.statistics_set.get(name='diameter')

class Statistics(models.Model):
    name = models.CharField(max_length=10)
    measurement = models.ForeignKey('Measurement')
    total = models.FloatField('Total', null=True, blank=True)
    avg = models.FloatField('Average', null=True, blank=True)
    min = models.FloatField('Minimum', null=True, blank=True)
    max = models.FloatField('Maximum', null=True, blank=True)
    standard_deviation = models.FloatField('Standard deviation', null=True, blank=True)

Once you fix the relationship between the objects the problem becomes much easier to solve. Instead of being ForeignKeyFields in the form, the Statisticss become proper related objects, which are routinely handled by django.

As @solarisssmoke mentioned in the comments you are looking for formsets. Here is an example from the django documentation showing how to achieve what you need:

The models in question:

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

And a view using inlineformset_factory to create the needed formset:

from django.forms import inlineformset_factory


def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something. Should generally end with a redirect. For example:
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render(request, 'manage_books.html', {'formset': formset})

If performance becomes an issue also have a look at prefetch_related for boosting performance.

Upvotes: 2

Related Questions