Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display an unknown number of fields in Django template with field content of another record as the label

I have a Django app which I want to be able to use in multiple instances. One model (Listing) can have a variable number of fields (for the different instances) but will then always have exactly those extra fields for the instance. I want these extra fields added through admin so I've created models like this:

class BespokeField (models.Model):
    name = models.CharField(
        max_length = 20,
        verbose_name = "Field Title"
    )

    def __unicode__(self):
        return self.name

class Listing (models.Model):
    name = models.CharField (
        verbose_name = 'Listing',
        max_length = 30
    )
    slug = models.SlugField (
        verbose_name = "Slug",
        allow_unicode = True,
        unique=True,
        blank=True,
        null=True
    )

class ListingBespokeField (models.Model):
    bespoke_field = models.ForeignKey(BespokeField)
    listing = models.ForeignKey(Listing)
    value = models.CharField (
        max_length = 60
    )

    def __unicode__(self):
        return u'%s | %s' % (self.listing.name, self.bespoke_field.name)

The theory is admin specifies the bespoke fields and these are then displayed to the user in forms. Within admin this is relatively simple as I can assume a modicum on intelligence from the users so my admin.py looks like:

class ListingBespokeFieldInline(admin.TabularInline):
    model = ListingBespokeField
    extra = len(BespokeField.objects.all())
    max_num = len(BespokeField.objects.all())

class ListingAdmin(admin.ModelAdmin):
    inlines = [ListingBespokeFieldInline]

It does mean the admin user has to select one of each BespokeField from the dropdown but I'm not uncomfortable with that because by using unique_together ensure there is only one of each.

What I cannot work out how to do is present this to the non-admin user in a friendly way. What I want is the BespokeField.name to display on the form as a label for ListingBespokeField.value.

This is what I have in forms.py (for ListingBespokeField).

class ListingBespokeFieldInline(forms.ModelForm):
    class Meta:
        model = ListingBespokeField
        exclude =['id']
        widgets = {
            'bespoke_field' : forms.HiddenInput(),
            'value' : forms.TextInput(attrs={'class' : 'form-control'})
        }

class ListingBespokeFieldForm(forms.ModelForm):
    class Meta:
        model = ListingBespokeField
        exclude = ()

BESPOKE_FIELD_COUNT = len(BespokeField.objects.all())

ListingBespokeFieldInlineFormSet = forms.inlineformset_factory (
    Listing,
    ListingBespokeField,
    form=ListingBespokeFieldInline,
    extra = BESPOKE_FIELD_COUNT,
    max_num = BESPOKE_FIELD_COUNT,
    exclude = ['id'],
    can_delete=False,
    can_order=False
)

I'm then trying to present it through a template as follows:

<table class="table">
    {{ bespokefields.management_form }}

    {% for form in bespokefields.forms %}
        {% if forloop.first %}
        <thead>
            <tr>
                {% for field in form.visible_fields %}
                    <th>{{ field.label|capfirst }}</th>
                {% endfor %}
            </tr>
        </thead>
        {% endif %}
        <tr class="formset_row bespokefield">
            <td>
                {{ form.listing }}{{ form.id }}{{ form.bespoke_field }}
                {{ form.bespoke_field.label }}
            </td>
            <td>{{ form.value }}</td>
        </tr>
    {% endfor %}
</table>

This doesn't work. I could use some insight please.

like image 540
HenryM Avatar asked Oct 17 '17 14:10

HenryM


1 Answers

This was my solution:

<table class="table">
    {{ bespokefields.management_form }}

    {% for form in bespokefields.forms %}
        <tr class="formset_row bespokefield">
            <td>
                {{ form.listing }}{{ form.id }}
                <select id="id_listingbespokefield_set-{{ forloop.counter0 }}-bespoke_field" name="listingbespokefield_set-{{ forloop.counter0 }}-bespoke_field" class="form-control">
                {% with forloop.counter as counter %}
                    {% for x,y in form.fields.bespoke_field.choices %}
                        {% if counter == forloop.counter0 %}
                            <option value="{{x}}" selected>{{y}}</option>
                        {% endif %}
                    {% endfor %}
                {% endwith %}
                </select>
            </td>
            <td>{{ form.value }}</td>
        </tr>
    {% endfor %}
</table>
like image 103
HenryM Avatar answered Oct 20 '22 04:10

HenryM