Импортируем CSV файл в Django используя панель администратора


16 января 2023


Выполняем импорт CSV в Django через панель администратора

Импорт данных на сайт - это частый функционал, который требуется множеством пользователей. В качестве формата файла, для импорта, используется Excel или CSV. Самое частое требование к задаче - это предварительная аутентификация пользователя или выполнение подобного импорта только через панель администратора. В Django, по умолчанию, нет возможности подобного импорта, но вы можете его реализовать не встречая особых сложностей руководствуясь примерами описанными ниже.

 

Варианты реализации импорта

В статье будет рассмотрена возможность реализации импорта в Django используя как свое решение, так и стороннего модуля. Оба варианта работают через панель администрирования и для аутентифицированных пользователей.

Формат данных, используемый в примерах в статье, CSV. Преимущества CSV над остальными форматами в том, что он поддерживается в качестве экспорта в большинстве программ (SQL/1C/Excel). Сам синтаксис формата простой.

Формат файлов, которые создают программы типа Excel и OpenOffice, для импорта подходят плохо. Каждая из этих программ использует разные форматы файлов, а их импорт может привести к серьезным ошибкам.

В статье используется Django 4.1, но для старых версий так же все должно подойти. Если вы используете Django < 3.0, то обратите внимание на BASE_DIR.

Тестовый проект

Если вы хотите посмотреть на работу примеров локально, то можете создать аналогичный тестовый проект используя код ниже.

# создаем проект
django-admin startproject csvimport
# переходим в проект и создаем приложение
cd csvimport
python manage.py startapp learn

Создание тестового проекта на Django

После создания приложения мы должны добавить его в конфигурацию Django проекта. Для этого нужно открыть файл 'csvimport/settings.py' и отредактировать строку 'INSTALLED_APPS', добавив в нее полное название созданного приложения.

INSTALLED_APPS = [
    .....
    'django.contrib.messages',
    # новая строка
    'learn.apps.LearnConfig',
]

Добавление приложения в INSTALLED_APPS в Django

Для примеров создана модель с названием 'Book'. Для этой модели и будет выполняться импорт. Модель прописывается в файле 'learn/models.py'.

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=150)
    author = models.CharField(max_length=150)
    publish_date = models.DateField()

Создание модели в Django

Чтобы эта модель отображалась в панели администрирования - ее нужно указать в файле 'learn/admin.py':

from django.contrib import admin
from .models import Book

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('name', 'author', 'publish_date',)

Вывод модели в панели администрирования Django

Для создания таблицы в базе данных, на основе модели выше, нужно выполнить миграции. Кроме этого нужно создать пользователя, который сможет заходить в панель администрирования.

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

Выполнение миграций в Django

 

Реализация импорта через свою функцию

Самостоятельно реализовать импорт можно через следующие шаги:

  • Создание формы, через которую будет выполняться импорт;
  • Создание двух шаблонов (за счет расширения и редактирования существующих шаблонов панели администрирования). Один шаблон будет содержать ссылку на страницу с формой, а второй шаблон - саму форму;
  • Создание функции, которая обработает данные и добавит их в базу.

Проект с кодом ниже можно так же посмотреть в моем репозитории на GitHub.

Создание формы

Чтобы хранить историю загруженных файлов и иметь возможность поиска ошибок - я добавил еще одну модель в 'learn/models.py'. Это необязательно, но полезно и используется в некоторых следующих примерах.

...
class BookImport(models.Model):
    csv_file = models.FileField(upload_to='uploads/')
    date_added = models.DateTimeField(auto_now_add=True)

Создание модели для хранения истории импорта Django

И выполнил миграцию:

python manage.py makemigrations
python manage.py migrate

Форма, через которую будет загружаться файл, будет привязана к этой модели. Форма объявляется в файле 'learn/forms.py', который создается самостоятельно.

from django.forms import ModelForm
from .models import BookImport

class BookImportForm(ModelForm):
    class Meta:
        model = BookImport
        fields = ('csv_file',)

Создание формы для импорта данных в Django

Создание шаблона

Место, где будут храниться шаблоны, прописывается в файле 'csvimport/settings.py'. Для этого нужно изменить значение в переменной 'TEMPLATES', а именно значение 'DIRS'. У меня оно следующее (в старых версиях Django, а именно младше 3.0, значение BASE_DIR менялось иначе).

'DIRS': [BASE_DIR / 'templates'],

Указание места хранения шаблонов Django

Шаблоны панели администрирования Django работают так же как и обычные, но находятся в папке с самим модулем (папка site-packages/django). Мы можем заменить их, отредактировать или расширить просто разместив по определенному пути в папке 'templates'.

Для работы формы импорта мы создадим новую страницу (шаблон) по следующему пути 'templates/admin/csv_import_page.html'. Эта страница расширяет функционал обычной страницы панели администратора дополнительно выводя форму. В этом файле находится следующий код.

{% extends 'admin/base.html' %}

{% block content %}
    <div>
        <form action="." method="POST" enctype="multipart/form-data">
            {{ form.as_p }}
            {% csrf_token %}
            <button type="submit">Загрузка CSV</button>
        </form>
    </div>
{% endblock %}

Создание шаблона для панели администрирования Django

Что бы пользователь мог попасть на страницу с формой - для него нужно создать кнопку. Пример ниже создает кнопку "Импорт" на странице, где выводятся все объекты из модели 'Book' приложения 'learn'. Такой тип страниц называется 'change_list.html'. Код, соответственно, находится по следующему пути 'templates/admin/learn/book/change_list.html'. В этом файле находится следующий код.

{% extends 'admin/change_list.html' %}
{% load static %}

{% block object-tools-items %}
<li>
    <a href="csv-upload/" class="addlink">
      Импорт
    </a>
</li>
{{ block.super }}
{% endblock %}

Изменение шаблона для панели администрирования Django

Шаблоны, которые были созданы выше, будут отображаться следующим образом. Вы сможете увидеть их работу после создания метода.

Пример импорта CSV в Django

Создание метода

Теперь нам нужно создать метод, который будет отправлять пользователю сгенерированный шаблон с формой и получать с формы данные. Один из вариантов реализации - сделать это в файле 'learn/admin.py', в классе для нужной модели. 

# обычные модули для панели администрирования
from django.contrib import admin
from .models import Book, BookImport

# обслуживание импорта
import csv
from .forms import BookImportForm
from django.urls import path
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.contrib import messages

# отображает панель для модели BookImport
@admin.register(BookImport)
class BookImportAdmin(admin.ModelAdmin):
    list_display = ('csv_file', 'date_added')

# отображает панель для модели Book и метод для импорта
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('name', 'author', 'publish_date',)
    
    # даем django(urlpatterns) знать
    # о существовании страницы с формой
    # иначе будет ошибка
    def get_urls(self):
        urls = super().get_urls()
        urls.insert(-1, path('csv-upload/', self.upload_csv))
        return urls

    # если пользователь открыл url 'csv-upload/'
    # то он выполнит этот метод
    # который работает с формой
    def upload_csv(self, request):
        if request.method == 'POST':
            # т.к. это метод POST проводим валидацию данных
            form = BookImportForm(request.POST, request.FILES)
            if form.is_valid():
                # ... 
                # какая-то ваша реализация обработки формы
                # ...

                # возвращаем пользователя на главную с сообщением об успехе
                url = reverse('admin:index')
                messages.success(request, 'Файл успешно импортирован')
                return HttpResponseRedirect(url)
        # если это не метод POST, то возвращается форма с шаблоном
        form = BookImportForm()
        return render(request, 'admin/csv_import_page.html', {'form': form})

Импорт CSV на основе функции для панели администрирования Django

В целом у вас могут возникнуть вопросы к двум методам:

  • get_urls() - это метод используется, что бы уведомить Django о новой странице в панели администрирования. Иначе у нас будет появляться ошибка.
  • upload_csv() - это обычный метод обработки формы, который вы так же можете реализовать во views.py и использовать наследование. Мы либо обрабатываем метод POST либо возвращаем саму форму.

Метод 'upload_csv()', выше, показан не полностью т.к. именно отсутствующая часть будет отличаться во всех проектах. Например, у вас может использоваться разделить либо запятая, либо точка с запятой и т.д.

Для тестового проекта, который создавался в начале, можно использовать следующий вариант 'upload_csv'. Он, например, проверяет, что в файле "csv" указаны верные заголовки в виде 'name,author,publish_date' и в качестве разделителя использует запятую.

    def upload_csv(self, request):
        if request.method == 'POST':
            form = BookImportForm(request.POST, request.FILES)
            if form.is_valid():
                # сохраняем загруженный файл и делаем запись в базу
                form_object = form.save()
                # обработка csv файла
                with form_object.csv_file.open('r') as csv_file:
                    rows = csv.reader(csv_file, delimiter=',')
                    if next(rows) != ['name', 'author', 'publish_date']:
                        # обновляем страницу пользователя
                        # с информацией о какой-то ошибке
                        messages.warning(request, 'Неверные заголовки у файла')
                        return HttpResponseRedirect(request.path_info)
                    for row in rows:
                        print(row[2])
                        # добавляем данные в базу
                        Book.objects.update_or_create(
                            name=row[0],
                            author=row[1],
                            publish_date=row[2]
                        )
                # конец обработки файлы
                # перенаправляем пользователя на главную страницу
                # с сообщением об успехе
                url = reverse('admin:index')
                messages.success(request, 'Файл успешно импортирован')
                return HttpResponseRedirect(url)
        form = BookImportForm()
        return render(request, 'admin/csv_import_page.html', {'form': form})

Импорт CSV на основе функции для панели администрирования Django с логикой

Вы можете создать файл CSV со следующими данными и попробовать его импортировать через панель администрирования модели 'Book'.

name,author,publish_date
Book1,Author1,2022-12-21
Book2,Author2,2022-12-22
Book3,Author3,2022-12-23
Book4,Author4,2022-12-24

В случае успеха вы увидите изменения на 3 страницах:

  1. Переадресация на главную страницу с сообщением об успехе;
  2. Загруженный файл в модели 'BookImport';
  3. Загруженные данные в модели 'Book'.

Просмотр истории импорта CSV в Django

В варианте выше проверяются только заголовки (первая строка в CSV). Если эта строка будет неверной, то вы сможете увидеть информацию о проблеме и посмотреть сам файл в 'BookImport'.

Пример валидации для импорта CSV в Django

 

 

Реализация CSV импорта через сторонний модуль

Для Django есть популярный модуль, имеющий более 2600 звезд на github, под названием 'django-import-export'. Его преимущество в том, что в нем реализован не только импорт в формате CSV, но так же YAML, XML и т.д. Кроме этого он содержит возможность оптимизации транзакций и логов, что удобно для случаев, когда вы импортируется большой объем данных. Так же есть предварительный просмотр импорта, экспорт и другие возможности...

Первое, что мы должны сделать - установить модуль.

pip install django-import-export

Установленный модуль мы должны добавить в 'INSTALLED_APPS' по пути 'csvimport/settings.py'.

INSTALLED_APPS = [
    ...
    'import_export',
]

Установка расширения в Django в INSTALLED_APPS

В этом же файле, setting.py, мы должны прописать путь 'STATIC_ROOT'. Эта папка, в которую будут собираться все статические файлы. Это нужно, если вы используете боевой сервер. Переменная для папки, часто, имеет следующий вид (для Django 3.0+).

STATIC_ROOT = BASE_DIR / 'static'

После создания папки - мы должны выполнить сбор всех статических файлов.

python manage.py collectstatic

Сбор статический файлов в Django

Самый простой способ создать импорт - это объявить следующие классы в файле 'learn/admin.py'.

from django.contrib import admin
from .models import Book
from import_export import resources
from import_export.admin import ImportExportModelAdmin

# класс обработки данных
class BookResource(resources.ModelResource):

    class Meta:
        model = Book

# вывод данных на странице
class BookAdmin(ImportExportModelAdmin):
    resource_classes = [BookResource]

admin.site.register(Book, BookAdmin)

Импорт в Django на основе стороннего модуля

Используя следующие данные вы можете попробовать выполнить импорт.

name,author,publish_date
Book1,Author1,2022-12-21
Book2,Author2,2022-12-22
Book3,Author3,2022-12-23
Book4,Author4,2022-12-24

Результат будет следующий.

Пример работы модуля для импорта данных в Django

Дополнительные возможности

У модуля есть достаточно много возможностей, которые часто требуются. Ниже описана только часть из них.

Через класс 'BookResource', который был создан в примере выше, можно выполнять не только импорт, но и экспорт. Импорт и экспорт можно выполнять и в обычных функциях. Пример с shell.

python manage.py shell
...
from learn.admin import BookResource
data = BookResource().export()
print(data.csv)

Пример работы модуля при экспорте в Django

По умолчанию дубликаты, которые могут присутствовать при импорте, не проверяются. Чтобы исключить значения, которые и так присутствуют в таблице, вы можете использовать следующие атрибуты:

  • skip_unchanged - будут ли пропускаться строки (row), которые уже присутствуют в базе;
  • report_skipped - вывод списка пропущенных строк.

По умолчанию идет проверка по полю 'id'. Учитывая, что идентификатор может не быть в файле импорта, можно указать другое значение. В примере ниже проверка будет проходить по названию 'name'.

# learn/admin.py
...
class BookResource(resources.ModelResource):

    class Meta:
        model = Book
        import_id_fields = ('name',)
        skip_unchanged = True
        report_skipped = True

Пример пропуска дублей при импорте в Django

Если в вашем файле CSV больше данных, чем требуется, то можно исключить их при импорте. В следующем примере будет импортирована только колонка 'name' т.к. остальные исключены.

# learn/admin.py
...
class BookResource(resources.ModelResource):

    class Meta:
        model = Book
        exclude = ('author', 'publish_date')

...

Теги: #python #django #csv


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, приобретённого опыта и лучшие практики в системном администрировании и программировании.