Search code examples
pythondjangofile-uploadadmin

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'


Solution

  • 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():
                    # process form
    

    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.