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

Предыстория

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

Некоторой проблемой для меня оказалось то, что я так до конца и не смог понять, как этим пользоваться. Мне было нужно получить импорт данных в любую из моделей, которых у нас было на тот момент, примерно 40. Из описания, доступного на тот момент я понял, что это кажется, можно сделать. Однако громоздкость набора использованных фреймворков и необходимость регистрации сериализаторов для каждой из моделей меня напрягли настолько, что я стал искать замену.

Надо было разобраться

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

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

Еще один пакет почему-то не оказался в специализированном гриде, но ищется поиском. Это Django CSV Import. Он понравился мне своим скупым дизайном и простотой использования, но отсутствие кастомизации импорта и передачи процедуры импорта в асинхронное исполнение через Celery потребовало вмешательства в код. Через некоторое время, проведенное в попытке такого вмешательства, я понял, что мне проще будет написать все с нуля, чем разбираться в зависимостях, которые образовались из за длительной истории этого пакета.

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

Процедура импорта

Начну с того, что сразу после установки и включения пакета а список INSTALLED_APPS, он позволяет импортировать данные почти в любую из моделей вашего проекта (кроме запрещенных по умолчанию совсем уж системных). Выбор модели производится из списка объектов ContentType, который пополняется самой Django при регистрации моделей, так что вам для этого ничего дополнительно делать не надо. Вы можете впрочем, установить свой собственный черный или белый список моделей, доступных для импорта в вашем проекте, через настройки проекта в файле settings.py, как обычно.

Диапазон входящих форматов файлов ограничивается возможностями библиотеки pandas. Ее набор функций импорта данных используется непосредственно для выполнения импорта. Любые дополнительные параметры для этих функций могут быть переданы в настройках задания импорта данных.

Сама процедура импорта выполняется либо сразу в момент сохранения задания, либо после сохранения объекта описания задания, в узле Celery. Последнее требует добавления одной строки в настройках, ну и конечно, запуска собственно самого узла.

Триггер, запускающий синхронный или асинхронный импорт, связан с сохранением объекта задания импорта, которое может быть выполнено с помощью любого доступного API.

Кастомизация импорта полей

По умолчанию, импорт происходит исходя из совпадения имен импортируемых колонок и полей импортируемой модели. Если пользователя это не устраивает, он может прямо в задании импорта задать тонкие настройки импорта, включая отображение имени колонки в имя поля, преобразование типа, форматирование текстовых полей, выбор значения из заданного списка, или формирование ссылки на другую модель поиском значения в поле другой модели. Вы можете импортировать даже сложные свойства (property) объекта, имеющие функцию обновления (setter). Это позволяет легко импортировать в том числе и определенные пользователем типы полей.

При импорте данных часто возникает проблема повторного импорта, когда повторно импортируемые данные должны обновлять существующие записи, а не создавать новые. Настройки задания импорта позволяют задать набор полей, используемый для однозначной идентификации объектов импорта. Если в базе есть объект, который в этих полях содержит те же значения, что и импортируемый объект, то вместо создания нового, используется найденный в базе объект.

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

Пользуйтесь!

Пакет имеет открытый код, размещен на GitHub и распространяется под лицензией LGPL. Его можно использовать непосредственно в своем проекте, расширяя своими разновидностями отображений по необходимости. Если у вас есть исправления, вы можете ответвиться и предложить ваши изменения. Если готовых изменений нет, можно написать Issue, где описать вашу проблему или идею.

Комментарии (5)


  1. danilovmy
    00.00.0000 00:00
    +2

    Привет. Хороший задел. Тоже используем свой рукописный импорт.

    вместо options в settings у нас модель importhelper. позволяет не морочиться с правкой settings и перестартом сервера. Рекомендую.

    Ну и на правах знатока django.admin:

    class ImportLogInline(StackedInline):

    def has_add_permission(request, *av, **kw):

    Нарушена сигнатура has_add_permission. Первым должен передаваться self. Да, я вижу что пофиг и return false. но все же.

    Ну и рекомендую подробнее разобраться с ContentType. Понимаю, что пакет применялся на маленьком проекте и без истории. Но у Django есть такое свойство, что на больших проектах бывает, что в таблице django_content_type лежат данные несуществующих моделей.

    Просто Django.objects.all() получает данные из локального списка моделей (ContentTypeManager._cache), в котором все проверено для текущего проекта.

    А вот ContentType.objects.all().annotate(...) тюкнется в базу и сможет получить записи удаленных моделей. Уверен, что ImportJobAdmin.formfield_for_foreignkey в текущем варианте на github.com/nnseva/django-import/blob/master/django_import/admin.py может привести к падению, как раз по этой причине.


    1. nnseva Автор
      00.00.0000 00:00

      вместо options в settings у нас модель importhelper. позволяет не морочиться с правкой settings и перестартом сервера. Рекомендую.

      Хм. Ну, скажем так, что касается настройки списка доступных к импорту моделей и хранилища файлов, так это вряд ли поменяется в рантайме, тем более что хранилище поменять в рантайме вообще будет весьма затруднительно.

      А вот флаг синхронного/асинхронного запуска импорта можно было бы включить в задание, но я подумал, что такая кастомизация только вредна, поскольку влияет на нагрузку узлов в большом распределенном проекте. По сути, синхронный запуск - это затычка для локально запущенного сервера, а продакшн должен конечно, запускаться с асинхронным запуском всегда.

      def has_add_permission(request, *av, **kw):

      Ох вот ведь, поймал :) Давай PR, замержу. Ну или как будет момент, сам поправлю.

      подробнее разобраться с ContentType

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

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


      1. danilovmy
        00.00.0000 00:00

        точно. Пермишны моделей вообще не проверяются, есть такая модель или нет. На Django Con EU я про это упоминал в докладе.


  1. yehitas
    00.00.0000 00:00

    Спасибо за еще одну интересную библиотеку! на pypi добавили?


    1. nnseva Автор
      00.00.0000 00:00

      Разумеется, всегда сразу все выкладываю

      https://pypi.org/project/django-import/