4

I have a django admin page which is for viewing purposes only and rather than displaying data from the model, it is displaying data for a table linked by a foreignkey to an intermediate table which is linked via a foreign key to my model. I want to apply a date range filter on the third table.

class Brand(models.Model):
   data ...

class Organisation (models.Model):
   data ...
   brand = models.ForeignKey(Brand, on_delete=models.CASCADE)

class DailyStat (models.Model):
   stat_type = model.CharField(max_lenth=11, choices=STAT_TYPE_CHOICES
   date = models.DateField()
   organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE)

I then created a change_form.html template in 'templates/admin/brand' which displays the data from DailyStat which I want for the Brand.

But I want to be able to filter this so I created a new form

class BrandAdminForm(forms.ModelForm):
    from_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
    to_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
    class Meta:
        model = Brand
        fields = ['id','from_date','to_date']

And within the BrandAdmin definition, referenced it

class BrandAdmin(admin.ModelAdmin):
    list_display = ['name','get_page_views','cqc_brand_id']
    ordering = ['name']
    search_fields = ['name']
    form = BrandAdminForm

These fields didn't automatically show in the detail page so I added the following within the form tags of {% block content %} of the change_form.html

<table style="width:60%">
    <tr>
        <td>From: </td>
        <td>{{ adminform.form.from_date }}</td>
        <td rowspan=2><button type="submit" value="Save and continue editing" class="btn viewsitelink">Filter</button></td>
    </tr>
    <tr>
        <td>To: </td>
        <td>{{ adminform.form.to_date }}</td>
    </tr>
</table>

So the fields now show in the form (I haven't written the processing to use the fields yet) BUT, I am running django-cms and when I click the filter button, it isn't returning to the pages under the django-cms admin panel rather than returning to the admin view.

If there is a better approach, how can I get to filtering the data I require more effectively OR what am I doing wrong that it isn't returning to the correct view (the form tag shows action="")

Thanks

1
  • Is the entire change view read only? If so there is a much better way to do this than the answer I have posted. Commented Jan 8, 2021 at 1:02

1 Answer 1

3
+50

This is definitely doable, but there are a few things you should consider:

  • This isn't really what a change-view is meant for. It feels weird from a UX perspective to have (on a page which is essentially one big form) form-elements unrelated to the form you are submitting.
  • django admin is not meant to be a user-facing production ready environment. If you're trying to make it do things it's not easy to do, it's normally a good sign you should be making your own views here.

The problem with your approach

The modelAdmin.form attribute, is meant to be a form which displays data from an instance of the relevant model, and which saves the returned POST data back to that same instance.

It isn't the form the view actually uses in the end. It is the form django-admin uses to build the form that is finally used though. The admin app does quite a bit of processing along the way, based off attributes that are set on modelAdmin so adding (unrelated) fields to modelAdmin.form won't necessarily correspond to fields on the finally rendered form.

If you want to add in to_date and from_date it should be done in a separate form. (For one thing, you otherwise wouldn't be able to change the dates without submitting your whole form).

Solution

You are far better to add a separate form, and use GET query parameters to update your dates. Something like this:

class DateForm(forms.Form):
    from_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
    to_date = forms.DateField(widget=admin.widgets.AdminDateWidget())

class BrandAdmin(admin.ModelAdmin):
    ...
    def change_view(self, request, object_id, form_url="", extra_context=None):
        date_form = DateForm(request.GET)
        if extra_context is None:
            extra_context = {}
        extra_context['date_form'] = date_form
        return super().change_view(
            request, object_id, form_url="", extra_context=extra_context
        )

Then in your change_form template

{% block content %}
<form method="get">
    {{date_form}}
    <input type="submit" value="Update dates">
</form>
{{ block.super }}
{% endblock %}

You will now have access to to_date and from_date in your template where you list your via request.GET and you should then be able to filter using them.

Not the answer you're looking for? Browse other questions tagged or ask your own question.