xgadam xgadam - 2 months ago 15
Python Question

Upload CSV file in django admin list view, replacing add object button

I want to replace the add object button in the listview of an admin page. The underlying idea is that an administrator can download data on all models in the db, use a tool to edit the data, and then reupload as a CSV file.

In the list view I am struggling to override the form, as setting

class SomeModelForm(forms.Form):
csv_file = forms.FileField(required=False, label="please select a file")

class Meta:
model = MyModel
fields = '__all__'

class SomeModel(admin.ModelAdmin):
change_list_template = 'admin/my_app/somemodel/change_list.html'

form = SomeModelForm

other stuff


The admin change_list.html is overridden as follows:

{% extends "admin/change_list.html" %}
{% load i18n admin_urls admin_static admin_list %}

{% block object-tools-items %}

<form action="{% url 'admin:custom_submit_row' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>
{{ form.as_p }}
</p>
<p><input type="submit" value="Upload" /><input type="reset" value="Reset"></p>
</form>
{% endblock %}


Previously SomeModel was missing the class Meta, as per sebbs response this is updated. The original error has been resolved but now currently the admin page is displaying the upload and reset buttons but no field for file uploads.

cheers

Edited with sebb's input below. Thanks sebb.
The error fixed was


< class ‘my_model.admin.SomeModelAdmin'>: (admin.E016) The value of 'form' must inherit from 'BaseModelForm'

Answer

OP here, solution is as follows:

class SomeModelForm(forms.Form):
csv_file = forms.FileField(required=False, label="please select a file")


class SomeModel(admin.ModelAdmin):
    change_list_template = 'admin/my_app/somemodel/change_list.html

    def get_urls(self):
        urls = super().get_urls()
        my_urls = patterns("",
                           url(r"^upload_csv/$", self.upload_csv, name='upload_csv')
                       )
        return my_urls + urls
    urls = property(get_urls)

    def changelist_view(self, *args, **kwargs):
        view = super().changelist_view(*args, **kwargs)
        view.context_data['submit_csv_form'] = SomeModelForm
        return view

    def upload_csv(self, request):
        if request.method == 'POST':
            form = MineDifficultyResourceForm(request.POST, request.FILES)
            if form.is_valid():
                do stuff

with the template overridden as so:

{% extends "admin/change_list.html" %}
{% load i18n admin_urls admin_static admin_list %}

{% block object-tools %}
    {% if has_add_permission %}
        <div>
            <ul class="object-tools">
                {% block object-tools-items %}
                    <form id="upload-csv-form" action="{% url 'admin:upload_csv' %}" method="post" enctype="multipart/form-data">
                    {% csrf_token %}
                        <p>{{ form.non_field_errors }}</p>
                        <p>{{ submit_csv_form.as_p }}</p>
                        <p>{{ submit_csv_form.csv_file.errors }}</p>
                        <p><input type="submit" value="Upload" />
                            <input type="reset" value="Reset"></p>
                    </form>
                {% endblock %}
            </ul>
        </div>
     {% endif %}
{% endblock %}

The form needs some custom validation but otherwise this solves the difficult part of customizing the admin page.

To elaborate what is going on here:

  1. get_urls is overridden so that an additional endpoint can be added to the admin page, this can point to any view, in this case it points upload_csv

  2. changelist_view is overridden to append the form info to the view

  3. the change_list.html template block "object-tools" is overridden with the form fields

Hopefully someone else finds this helpful as well.

Comments