Improve docs.
This commit is contained in:
parent
ec990d8b99
commit
4a8af98955
15
README.rst
15
README.rst
|
@ -4,10 +4,19 @@ django-import-export
|
|||
|
||||
.. image:: https://travis-ci.org/django-import-export/django-import-export.svg?branch=master
|
||||
:target: https://travis-ci.org/django-import-export/django-import-export
|
||||
:alt: Build status on Travis-CI
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/django-import-export.svg
|
||||
:target: https://pypi.python.org/pypi/django-import-export
|
||||
:alt: Current version on PyPi
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/django-import-export.svg
|
||||
:target: https://pypi.python.org/pypi/django-import-export
|
||||
:target: https://pypi.python.org/pypi/django-import-export
|
||||
:alt: Downloads per month on PyPi
|
||||
|
||||
.. image:: http://readthedocs.org/projects/django-import-export/badge/?version=latest
|
||||
:target: http://django-import-export.rtfd.org
|
||||
:alt: Docmentation
|
||||
|
||||
django-import-export is a Django application and library for importing
|
||||
and exporting data with included admin integration.
|
||||
|
@ -56,6 +65,10 @@ If you'd like to contribute, simply fork `the repository`_, commit your
|
|||
changes to the **develop** branch (or branch off of it), and send a pull
|
||||
request. Make sure you add yourself to AUTHORS_.
|
||||
|
||||
As most projects, we try to follow PEP8_ as closely as possible. Please bear
|
||||
in mind that most pull requests will be rejected without proper unit testing.
|
||||
|
||||
.. _`PEP8`: https://www.python.org/dev/peps/pep-0008/
|
||||
.. _`tablib`: https://github.com/kennethreitz/tablib
|
||||
.. _`the repository`: https://github.com/django-import-export/django-import-export/
|
||||
.. _AUTHORS: https://github.com/django-import-export/django-import-export/blob/master/AUTHORS
|
||||
|
|
|
@ -2,5 +2,7 @@
|
|||
Admin
|
||||
=====
|
||||
|
||||
For instructions on how to use the models and mixins in this module, please refer to :ref:`admin-integration`.
|
||||
|
||||
.. automodule:: import_export.admin
|
||||
:members:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
============
|
||||
Tmp storages
|
||||
============
|
||||
==================
|
||||
Temporary storages
|
||||
==================
|
||||
|
||||
.. currentmodule:: import_export.tmp_storages
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ Widgets
|
|||
.. autoclass:: import_export.widgets.TimeWidget
|
||||
:members:
|
||||
|
||||
.. autoclass:: import_export.widgets.DateTimeWidget
|
||||
:members:
|
||||
|
||||
.. autoclass:: import_export.widgets.ForeignKeyWidget
|
||||
:members:
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Changelog for django-import-export
|
||||
==================================
|
||||
Changelog
|
||||
=========
|
||||
|
||||
0.4.3 (unreleased)
|
||||
------------------
|
||||
|
@ -12,6 +12,7 @@ Changelog for django-import-export
|
|||
|
||||
- Add support for django.db.models.TimeField (#381)
|
||||
|
||||
|
||||
0.4.2 (2015-12-18)
|
||||
------------------
|
||||
|
||||
|
@ -89,7 +90,7 @@ Changelog for django-import-export
|
|||
|
||||
- added use of get_diff_headers method into import.html template (#158)
|
||||
|
||||
- Try to use OrderedDict instead of SortedDict, which is deprecated in
|
||||
- Try to use OrderedDict instead of SortedDict, which is deprecated in
|
||||
Django 1.7 (#157)
|
||||
|
||||
- fixed #105 unicode import
|
||||
|
@ -112,9 +113,9 @@ Changelog for django-import-export
|
|||
|
||||
- Fixed XLS import on python 3. Optimized loop
|
||||
|
||||
- Fixed properly skipping row marked as skipped when importing data from
|
||||
- Fixed properly skipping row marked as skipped when importing data from
|
||||
the admin interface.
|
||||
|
||||
|
||||
- Allow Resource.export to accept iterables as well as querysets
|
||||
|
||||
- Improve error messages
|
||||
|
|
|
@ -35,7 +35,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'django-import-export'
|
||||
copyright = u'2012, Bojan Mihelac'
|
||||
copyright = u'2012–2016, Bojan Mihelac'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
@ -90,6 +90,7 @@ pygments_style = 'sphinx'
|
|||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
Configuration
|
||||
=============
|
||||
|
||||
You only need to perform this configuration step if you use django-import-export in the admin.
|
||||
|
||||
Add ``import_export`` to your ``INSTALLED_APPS``:
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# ...
|
||||
|
||||
'import_export',
|
||||
]
|
||||
|
||||
Deploy static files:
|
||||
|
||||
$ python manage.py collectstatic
|
|
@ -1,10 +0,0 @@
|
|||
=============
|
||||
Contributing
|
||||
=============
|
||||
|
||||
Code guidelines
|
||||
---------------
|
||||
|
||||
* As most projects, we try to follow PEP8 as closely as possible
|
||||
|
||||
* Most pull requests will be rejected without proper unit testing
|
|
@ -1,9 +0,0 @@
|
|||
===========
|
||||
Example app
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
cd tests && ./manage.py runserver
|
||||
|
||||
Username and password for admin are 'admin', 'password'.
|
|
@ -3,7 +3,9 @@ Getting started
|
|||
===============
|
||||
|
||||
For example purposes, we'll use a simplified book app. Here is our
|
||||
``core.models.py``::
|
||||
``models.py``::
|
||||
|
||||
# app/models.py
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
@ -25,8 +27,7 @@ For example purposes, we'll use a simplified book app. Here is our
|
|||
author_email = models.EmailField('Author email', max_length=75, blank=True)
|
||||
imported = models.BooleanField(default=False)
|
||||
published = models.DateField('Published', blank=True, null=True)
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, null=True,
|
||||
blank=True)
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
||||
categories = models.ManyToManyField(Category, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
|
@ -36,27 +37,27 @@ For example purposes, we'll use a simplified book app. Here is our
|
|||
.. _base-modelresource:
|
||||
|
||||
Creating import-export resource
|
||||
-------------------------------
|
||||
===============================
|
||||
|
||||
To integrate `django-import-export` with ``Book`` model, we will create
|
||||
a resource class in ``admin.py`` that will describe how this resource can be imported or
|
||||
exported.
|
||||
To integrate `django-import-export` with our ``Book`` model, we will create a
|
||||
:class:`~import_export.resources.ModelResource` class in ``admin.py`` that will
|
||||
describe how this resource can be imported or exported::
|
||||
|
||||
::
|
||||
# app/admin.py
|
||||
|
||||
from import_export import resources
|
||||
from core.models import Book
|
||||
|
||||
|
||||
class BookResource(resources.ModelResource):
|
||||
|
||||
class Meta:
|
||||
model = Book
|
||||
|
||||
Exporting data
|
||||
--------------
|
||||
==============
|
||||
|
||||
Now that we have defined a resource class, we can export books::
|
||||
Now that we have defined a :class:`~import_export.resources.ModelResource` class,
|
||||
we can export books::
|
||||
|
||||
>>> dataset = BookResource().export()
|
||||
>>> print dataset.csv
|
||||
|
@ -64,11 +65,11 @@ Now that we have defined a resource class, we can export books::
|
|||
2,Some book,1,,0,2012-12-05,8.85,1
|
||||
|
||||
Customize resource options
|
||||
--------------------------
|
||||
==========================
|
||||
|
||||
By default ``ModelResource`` introspects model fields and creates
|
||||
``import_export.fields.Field`` attributes with an appropriate widget
|
||||
for each field.
|
||||
By default :class:`~import_export.resources.ModelResource` introspects model
|
||||
fields and creates :class:`~import_export.fields.Field`-attributes with an
|
||||
appropriate :class:`~import_export.widgets.Widget` for each field.
|
||||
|
||||
To affect which model fields will be included in an import-export
|
||||
resource, use the ``fields`` option to whitelist fields::
|
||||
|
@ -105,7 +106,7 @@ The default field for object identification is ``id``, you can optionally set wh
|
|||
import_id_fields = ('isbn',)
|
||||
fields = ('isbn', 'name', 'author', 'price',)
|
||||
|
||||
When defining ``ModelResource`` fields it is possible to follow
|
||||
When defining :class:`~import_export.resources.ModelResource` fields it is possible to follow
|
||||
model relationships::
|
||||
|
||||
class BookResource(resources.ModelResource):
|
||||
|
@ -135,10 +136,10 @@ whether skipped records will show in the import preview page::
|
|||
.. seealso::
|
||||
|
||||
:doc:`/api_resources`
|
||||
|
||||
|
||||
|
||||
Declaring fields
|
||||
----------------
|
||||
================
|
||||
|
||||
It is possible to override a resource field to change some of its
|
||||
options::
|
||||
|
@ -147,14 +148,14 @@ options::
|
|||
|
||||
class BookResource(resources.ModelResource):
|
||||
published = fields.Field(column_name='published_date')
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Book
|
||||
|
||||
Other fields that don't exist in the target model may be added::
|
||||
|
||||
from import_export import fields
|
||||
|
||||
|
||||
class BookResource(resources.ModelResource):
|
||||
myfield = fields.Field(column_name='myfield')
|
||||
|
||||
|
@ -168,7 +169,7 @@ Other fields that don't exist in the target model may be added::
|
|||
|
||||
|
||||
Advanced data manipulation
|
||||
--------------------------
|
||||
==========================
|
||||
|
||||
Not all data can be easily extracted from an object/model attribute.
|
||||
In order to turn complicated data model into a (generally simpler) processed
|
||||
|
@ -178,7 +179,7 @@ data structure, ``dehydrate_<fieldname>`` method should be defined::
|
|||
|
||||
class BookResource(resources.ModelResource):
|
||||
full_title = fields.Field()
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Book
|
||||
|
||||
|
@ -187,11 +188,11 @@ data structure, ``dehydrate_<fieldname>`` method should be defined::
|
|||
|
||||
|
||||
Customize widgets
|
||||
-----------------
|
||||
=================
|
||||
|
||||
``ModelResource`` creates a field with a default widget for a given field
|
||||
type. If the widget should be initialized with different arguments, set the
|
||||
``widgets`` dict.
|
||||
A :class:`~import_export.resources.ModelResource` creates a field with a
|
||||
default widget for a given field type. If the widget should be initialized
|
||||
with different arguments, set the ``widgets`` dict.
|
||||
|
||||
In this example widget, the ``published`` field is overriden to use a
|
||||
different date format. This format will be used both for importing
|
||||
|
@ -200,7 +201,7 @@ and exporting resource.
|
|||
::
|
||||
|
||||
class BookResource(resources.ModelResource):
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Book
|
||||
widgets = {
|
||||
|
@ -213,66 +214,78 @@ and exporting resource.
|
|||
available widget types and options.
|
||||
|
||||
Importing data
|
||||
--------------
|
||||
==============
|
||||
|
||||
Let's import data::
|
||||
|
||||
>>> import tablib
|
||||
>>> from import_export import resources
|
||||
>>> from core.models import Book
|
||||
>>> book_resource = resources.modelresource_factory(model=Book)()
|
||||
>>> dataset = tablib.Dataset(['', 'New book'], headers=['id', 'name'])
|
||||
>>> book_resource = resources.modelresource_factory(model=Book)() # Line 4
|
||||
>>> dataset = tablib.Dataset( # Line 5
|
||||
... ['', 'New book'], headers=['id', 'name']
|
||||
... )
|
||||
>>> result = book_resource.import_data(dataset, dry_run=True)
|
||||
>>> print result.has_errors()
|
||||
False
|
||||
>>> result = book_resource.import_data(dataset, dry_run=False)
|
||||
|
||||
In 4th line we use ``modelresource_factory`` to create a default
|
||||
``ModelResource``. ModelResource class created this way is equal
|
||||
as in :ref:`base-modelresource`.
|
||||
In the fourth line we use :func:`~import_export.resources.modelresource_factory`
|
||||
to create a default :class:`~import_export.resources.ModelResource`.
|
||||
The ModelResource class created this way is equal to the one shown in the
|
||||
example in section :ref:`base-modelresource`.
|
||||
|
||||
In 5th line a ``Dataset`` with subset of ``Book`` fields is created.
|
||||
In fifth line a :class:`~tablib.Dataset` with columns ``id`` and ``name``, and one book entry, are created. A field for a primary key field (in this case, ``id``) always needs to be present.
|
||||
|
||||
In rest of code we first pretend to import data with ``dry_run`` set, then
|
||||
check for any errors and import data.
|
||||
In the rest of the code we first pretend to import data using
|
||||
:meth:`~import_export.resources.Resource.import_data` and ``dry_run`` set,
|
||||
then check for any errors and actually import data this time.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`/import_workflow`
|
||||
for detailed import workflow descripton and customization options.
|
||||
for a detailed description of the import workflow and its customization options.
|
||||
|
||||
|
||||
Deleting data
|
||||
^^^^^^^^^^^^^
|
||||
-------------
|
||||
|
||||
To delete objects during import, implement ``for_delete`` method on resource
|
||||
class.
|
||||
To delete objects during import, implement the
|
||||
:meth:`~import_export.resources.Resource.for_delete` method on
|
||||
your :class:`~import_export.resources.Resource` class.
|
||||
|
||||
Example resource with ``delete`` field::
|
||||
The following is an example resource which expects a ``delete`` field in the
|
||||
dataset. An import using this resource will delete model instances for rows
|
||||
that have their column ``delete`` set to ``1``::
|
||||
|
||||
class BookResource(resources.ModelResource):
|
||||
delete = fields.Field(widget=widgets.BooleanWidget())
|
||||
|
||||
def for_delete(self, row, instance):
|
||||
return self.fields['delete'].clean(row)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Book
|
||||
|
||||
Import of this resource will delete model instances for rows
|
||||
that have column ``delete`` set to ``1``.
|
||||
|
||||
.. _admin-integration:
|
||||
|
||||
Admin integration
|
||||
-----------------
|
||||
=================
|
||||
|
||||
Admin integration is achieved by subclassing (in ``admin.py``)
|
||||
``ImportExportModelAdmin`` or one of the available mixins (``ImportMixin``,
|
||||
``ExportMixin``, or ``ImportExportMixin``)::
|
||||
Exporting via list filters
|
||||
--------------------------
|
||||
|
||||
Admin integration is achieved by subclassing
|
||||
:class:`~import_export.admin.ImportExportModelAdmin` or one of the available
|
||||
mixins (:class:`~import_export.admin.ImportMixin`,
|
||||
:class:`~import_export.admin.ExportMixin`,
|
||||
:class:`~import_export.admin.ImportExportMixin`)::
|
||||
|
||||
# app/admin.py
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
|
||||
|
||||
class BookAdmin(ImportExportModelAdmin):
|
||||
resource_class = BookResource
|
||||
pass
|
||||
|
||||
.. figure:: _static/images/django-import-export-change.png
|
||||
|
@ -287,18 +300,19 @@ Admin integration is achieved by subclassing (in ``admin.py``)
|
|||
|
||||
A screenshot of the confirm import view.
|
||||
|
||||
|
|
||||
|
||||
Exporting via admin action
|
||||
--------------------------
|
||||
|
||||
Another approach to exporting data is by subclassing
|
||||
``ImportExportActionModelAdmin`` which implements export as an admin action.
|
||||
As a result it's possible to export a list of objects selected on the change
|
||||
list page::
|
||||
:class:`~import_export.admin.ImportExportActionModelAdmin` which implements
|
||||
export as an admin action. As a result it's possible to export a list of
|
||||
objects selected on the change list page::
|
||||
|
||||
# app/admin.py
|
||||
from import_export.admin import ImportExportActionModelAdmin
|
||||
|
||||
|
||||
class BookAdmin(ImportExportActionModelAdmin):
|
||||
resource_class = BookResource
|
||||
pass
|
||||
|
||||
|
||||
|
@ -306,7 +320,6 @@ list page::
|
|||
|
||||
A screenshot of the change view with Import and Export as an admin action.
|
||||
|
||||
|
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
|
|
@ -2,117 +2,112 @@
|
|||
Import data workflow
|
||||
====================
|
||||
|
||||
This document describes import data workflow, with hooks that enable
|
||||
customization of import process.
|
||||
This document describes the import data workflow in detail, with hooks that enable
|
||||
customization of the import process. The central aspect of the import process is a resource's
|
||||
:meth:`~import_export.resources.Resource.import_data` method which is explained below.
|
||||
|
||||
``import_data`` method arguments
|
||||
--------------------------------
|
||||
.. function:: import_data(dataset, dry_run=False, raise_errors=False)
|
||||
|
||||
``import_data`` method of :class:`import_export.resources.Resource` class is
|
||||
responsible for import data from given `dataset`.
|
||||
The :meth:`~import_export.resources.Resource.import_data` method of
|
||||
:class:`~import_export.resources.Resource` is responsible for importing data
|
||||
from a given dataset.
|
||||
|
||||
``import_data`` expect following arguments:
|
||||
``dataset`` is required and expected to be a :class:`tablib.Dataset` with
|
||||
a header row.
|
||||
|
||||
:attr:`dataset`
|
||||
REQUIRED.
|
||||
should be Tablib `Dataset`_ object with header row.
|
||||
``dry_run`` is a Boolean which determines if changes to the database are
|
||||
made or if the import is only simulated. It defaults to ``False``.
|
||||
|
||||
:attr:`dry_run`
|
||||
If ``True``, import should not change database. Default is ``False``.
|
||||
``raise_errors`` is a Boolean. If ``True``, import should raise errors.
|
||||
The default is ``False``, which means that eventual errors and traceback
|
||||
will be saved in ``Result`` instance.
|
||||
|
||||
:attr:`raise_errors`
|
||||
If ``True``, import should raise errors. Default is ``False``, which
|
||||
means that eventual errors and traceback will be saved in ``Result``
|
||||
instance.
|
||||
|
||||
``import_data`` method workflow
|
||||
-------------------------------
|
||||
This is what happens when the method is invoked:
|
||||
|
||||
#. ``import_data`` intialize new :class:`import_export.results.Result`
|
||||
instance. ``Result`` instance holds errors and other information
|
||||
gathered during import.
|
||||
#. First, a new :class:`~import_export.results.Result` instance, which holds
|
||||
errors and other information gathered during the import, is initialized.
|
||||
|
||||
#. ``InstanceLoader`` responsible for loading existing instances
|
||||
is intitalized.
|
||||
|
||||
Different ``InstanceLoader`` class
|
||||
can be specified with ``instance_loader_class``
|
||||
option of :class:`import_export.resources.ResourceOptions`.
|
||||
|
||||
:class:`import_export.instance_loaders.CachedInstanceLoader` can be used to
|
||||
Then, an :class:`~import_export.instance_loaders.InstanceLoader` responsible for loading existing instances
|
||||
is intitalized. A different :class:`~import_export.instance_loaders.BaseInstanceLoader` can be specified via
|
||||
:class:`~import_export.resources.ResourceOptions`'s ``instance_loader_class`` attribute.
|
||||
A :class:`~import_export.instance_loaders.CachedInstanceLoader` can be used to
|
||||
reduce number of database queries.
|
||||
See the `source <https://github.com/django-import-export/django-import-export/blob/master/import_export/instance_loaders.py>`_ for available implementations.
|
||||
|
||||
See :mod:`import_export.instance_loaders` for available implementations.
|
||||
#. The :meth:`~import_export.resources.Resource.before_import` hook is called.
|
||||
By implementing this method in your resource, you can customize the import process.
|
||||
|
||||
#. ``import_data`` calls the ``before_import`` hook method which by default does
|
||||
not do anything but can be overriden to customize the import process. The
|
||||
method receives the ``dataset`` and ``dry_run`` arguments as well as any
|
||||
additional keyword arguments passed to ``import_data`` in a ``kwargs`` dict.
|
||||
#. Each row of the to-be-imported dataset is processed according to the following steps:
|
||||
|
||||
#. Process each `row` in ``dataset``
|
||||
#. :meth:`~import_export.resources.Resource.get_or_init_instance` is called
|
||||
with current :class:`~import_export.instance_loaders.BaseInstanceLoader`
|
||||
and current row of the dataset, returning an object and a Boolean
|
||||
declaring if the object is newly created or not.
|
||||
|
||||
#. ``get_or_init_instance`` method is called with current ``InstanceLoader``
|
||||
and current `row` returning object `instance` and `Boolean` variable
|
||||
that indicates if object instance is new.
|
||||
If no object can be found for the current row,
|
||||
:meth:`~import_export.resources.Resource.init_instance` is invoked to
|
||||
initialize an object.
|
||||
|
||||
``get_or_init_instance`` tries to load instance for current `row` or
|
||||
calls ``init_instance`` to init object if object does not exists yet.
|
||||
As always, you can override the implementation of
|
||||
:meth:`~import_export.resources.Resource.init_instance` to customized
|
||||
how the new object is created (i.e. set default values).
|
||||
|
||||
Default ``ModelResource.init_instance`` initialize Django Model without
|
||||
arguments. You can override ``init_instance`` method to manipulate how
|
||||
new objects are initialized (ie: to set default values).
|
||||
#. :meth:`~import_export.resources.Resource.for_delete` is called to determine if the passed ``instance``
|
||||
should be deleted. In this case, the import process for the current row is stopped at this point.
|
||||
|
||||
#. ``for_delete`` method is called to determine if current `instance`
|
||||
should be deleted:
|
||||
#. If the instance was not deleted in the previous step,
|
||||
:meth:`~import_export.resources.Resource.import_obj` is called with the
|
||||
``instance`` as current object, ``row`` as current row and ``dry run``.
|
||||
|
||||
#. current `instance` is deleted
|
||||
|
||||
OR
|
||||
|
||||
#. ``import_obj`` method is called with the current object ``instance`` and
|
||||
current ``row`` and ``dry run`` arguments.
|
||||
|
||||
``import_obj`` loop through all `Resource` `fields`, skipping
|
||||
many to many fields and calls ``import_field`` for each. (Many to many
|
||||
fields require that instance have a primary key, this is why assigning
|
||||
them is postponed, after object is saved).
|
||||
|
||||
``import_field`` calls ``field.save`` method, if ``field`` has
|
||||
both `attribute` and field `column_name` exists in given row.
|
||||
|
||||
#. ``skip_row`` method is called with current object ``instance`` and
|
||||
original object ``original`` to determine if the row should be skipped
|
||||
|
||||
#. ``row_result.import_type`` is set to ``IMPORT_TYPE_SKIP``
|
||||
|
||||
OR
|
||||
|
||||
#. ``save_instance`` method is called.
|
||||
|
||||
``save_instance`` receives ``dry_run`` argument and actually saves
|
||||
instance only when ``dry_run`` is False.
|
||||
|
||||
``save_instance`` calls two hooks methods that by default does not
|
||||
do anything but can be overriden to customize import process:
|
||||
|
||||
* ``before_save_instance``
|
||||
|
||||
* ``after_save_instance``
|
||||
|
||||
Both methods receive ``instance`` and ``dry_run`` arguments.
|
||||
|
||||
#. ``save_m2m`` method is called to save many to many fields.
|
||||
|
||||
#. ``RowResult`` is assigned with diff between original and imported
|
||||
object fields as well as import type(new, updated, skipped).
|
||||
|
||||
If exception is raised inside row processing, and ``raise_errors`` is
|
||||
``False`` (default), traceback is appended to ``RowResult``.
|
||||
|
||||
If the row was not skipped or the `Resource` is configured to report
|
||||
skipped rows the ``RowResult`` is appended to the ``result``
|
||||
:meth:`~import_export.resources.Resource.import_field` is called for
|
||||
each field in :class:`~import_export.resources.Resource` skipping many-
|
||||
to-many fields. Many-to-many fields are skipped because they require
|
||||
instances to have a primary key and therefore assignment is postponed to
|
||||
when the object has already been saved.
|
||||
|
||||
#. ``result`` is returned.
|
||||
:meth:`~import_export.resources.Resource.import_field` in turn calls
|
||||
:meth:`~import_export.fields.Field.save`, if ``Field.attribute`` is set
|
||||
and ``Field.column_name`` exists in the given row.
|
||||
|
||||
#. It then is determined whether the newly imported object is different
|
||||
from the already present object and if therefore the given row should be
|
||||
skipped or not. This is handled by calling
|
||||
:meth:`~import_export.resources.Resource.skip_row` with ``original`` as
|
||||
the original object and ``instance`` as the current object from the dataset.
|
||||
|
||||
If the current row is to be skipped, ``row_result.import_type`` is set
|
||||
to ``IMPORT_TYPE_SKIP``.
|
||||
|
||||
#. If the current row is not to be skipped,
|
||||
:meth:`~import_export.resources.Resource.save_instance` is called and
|
||||
actually saves the instance when ``dry_run`` is not set.
|
||||
|
||||
There are two hook methods (that by default do nothing) giving you the option to customize the
|
||||
import process:
|
||||
|
||||
* :meth:`~import_export.resources.Resource.before_save_instance`
|
||||
* :meth:`~import_export.resources.Resource.after_save_instance`
|
||||
|
||||
Both methods receive ``instance`` and ``dry_run`` arguments.
|
||||
|
||||
#. :meth:`~import_export.resources.Resource.save_m2m` is called to save
|
||||
many to many fields.
|
||||
|
||||
#. :class:`~import_export.results.RowResult` is assigned with a diff
|
||||
between the original and the imported object fields, as well as and
|
||||
``import_type`` attribute which states whether the row is new, updated,
|
||||
skipped or deleted.
|
||||
|
||||
If an exception is raised during row processing and
|
||||
:meth:`~import_export.resources.Resource.import_data` was invoked with
|
||||
``raise_errors=False`` (which is the default) the particular traceback
|
||||
is appended to :class:`~import_export.results.RowResult` as well.
|
||||
|
||||
If either the row was not skipped or the
|
||||
:class:`~import_export.resources.Resource` is configured to report
|
||||
skipped rows, the :class:`~import_export.results.RowResult` is appended to the :class:`~import_export.results.Result`
|
||||
#. The :class:`~import_export.results.Result` is returned.
|
||||
|
||||
Transaction support
|
||||
-------------------
|
||||
|
|
|
@ -5,52 +5,44 @@ Django import / export
|
|||
django-import-export is a Django application and library for importing
|
||||
and exporting data with included admin integration.
|
||||
|
||||
Features:
|
||||
**Features:**
|
||||
|
||||
* support multiple formats (Excel, CSV, JSON, ...
|
||||
and everything else that `tablib`_ supports)
|
||||
* support multiple formats (Excel, CSV, JSON, ...
|
||||
and everything else that `tablib`_ supports)
|
||||
|
||||
* admin integration for importing
|
||||
* admin integration for importing
|
||||
|
||||
* preview import changes
|
||||
* preview import changes
|
||||
|
||||
* admin integration for exporting
|
||||
* admin integration for exporting
|
||||
|
||||
* export data respecting admin filters
|
||||
* export data respecting admin filters
|
||||
|
||||
.. figure:: _static/images/django-import-export-change.png
|
||||
.. figure:: _static/images/django-import-export-change.png
|
||||
|
||||
A screenshot of the change view with Import and Export buttons.
|
||||
A screenshot of the change view with Import and Export buttons.
|
||||
|
||||
User Guide
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: User Guide
|
||||
|
||||
installation
|
||||
configuration
|
||||
getting_started
|
||||
import_workflow
|
||||
example_app
|
||||
settings
|
||||
todo
|
||||
contributing
|
||||
changelog
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: API documentation
|
||||
|
||||
api_resources
|
||||
api_fields
|
||||
api_widgets
|
||||
api_instance_loaders
|
||||
api_admin
|
||||
api_results
|
||||
api_resources
|
||||
api_widgets
|
||||
api_fields
|
||||
api_instance_loaders
|
||||
api_tmp_storages
|
||||
api_results
|
||||
|
||||
|
||||
.. _`tablib`: https://github.com/kennethreitz/tablib
|
||||
|
|
|
@ -1,10 +1,69 @@
|
|||
============
|
||||
Installation
|
||||
============
|
||||
==============================
|
||||
Installation and configuration
|
||||
==============================
|
||||
|
||||
django-import-export is on the Python Package Index (PyPI),
|
||||
so it can be installed with standard Python tools like pip or easy_install:
|
||||
|
||||
::
|
||||
django-import-export is available on the Python Package Index (PyPI), so it
|
||||
can be installed with standard Python tools like ``pip`` or ``easy_install``::
|
||||
|
||||
$ pip install django-import-export
|
||||
|
||||
Alternatively, you can install the git repository directly to obtain the
|
||||
development version::
|
||||
|
||||
$ pip install -e git+https://github.com/django-import-export/django-import-export.git#egg=django-import-export
|
||||
|
||||
Now, you're good to go, unless you want to use django-import-export from the
|
||||
admin as well. In this case, you need to add it to your ``INSTALLED_APPS`` and
|
||||
let Django collect its static files.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# settings.py
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'import_export',
|
||||
)
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ python manage.py collectstatic
|
||||
|
||||
All prequisites are set up! See :doc:`getting_started` to learn how to use django-import-export in your project.
|
||||
|
||||
|
||||
|
||||
Settings
|
||||
========
|
||||
|
||||
You can use the following directives in your settings file:
|
||||
|
||||
``IMPORT_EXPORT_USE_TRANSACTIONS``
|
||||
Global setting controls if resource importing should use database
|
||||
transactions. Default is ``False``.
|
||||
|
||||
``IMPORT_EXPORT_SKIP_ADMIN_LOG``
|
||||
Global setting controls if creating log entries for
|
||||
the admin changelist should be skipped when importing resource.
|
||||
The `skip_admin_log` attribute of `ImportMixin` is checked first,
|
||||
which defaults to ``None``. If not found, this global option is used.
|
||||
This will speed up importing large datasets, but will lose
|
||||
changing logs in the admin changelist view. Default is ``False``.
|
||||
|
||||
``IMPORT_EXPORT_TMP_STORAGE_CLASS``
|
||||
Global setting for the class to use to handle temporary storage
|
||||
of the uploaded file when importing from the admin using an
|
||||
`ImportMixin`. The `tmp_storage_class` attribute of `ImportMixin`
|
||||
is checked first, which defaults to ``None``. If not found, this
|
||||
global option is used. Default is ``TempFolderStorage``.
|
||||
|
||||
|
||||
|
||||
Example app
|
||||
===========
|
||||
|
||||
There's an example application that showcases what django-import-export can do. You can run it via::
|
||||
|
||||
cd tests
|
||||
./manage.py runserver
|
||||
|
||||
Username and password for admin are ``admin`` and ``password``.
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
========
|
||||
Settings
|
||||
========
|
||||
|
||||
``IMPORT_EXPORT_USE_TRANSACTIONS``
|
||||
Global setting controls if resource importing should use database
|
||||
transactions. Default is ``False``.
|
||||
|
||||
``IMPORT_EXPORT_SKIP_ADMIN_LOG``
|
||||
Global setting controls if creating log entries for
|
||||
the admin changelist should be skipped when importing resource.
|
||||
The `skip_admin_log` attribute of `ImportMixin` is checked first,
|
||||
which defaults to ``None``. If not found, this global option is used.
|
||||
This will speed up importing large datasets, but will lose
|
||||
changing logs in the admin changelist view. Default is ``False``.
|
||||
|
||||
``IMPORT_EXPORT_TMP_STORAGE_CLASS``
|
||||
Global setting for the class to use to handle temporary storage
|
||||
of the uploaded file when importing from the admin using an
|
||||
`ImportMixin`. The `tmp_storage_class` attribute of `ImportMixin`
|
||||
is checked first, which defaults to ``None``. If not found, this
|
||||
global option is used. Default is ``TempFolderStorage``.
|
|
@ -1,4 +0,0 @@
|
|||
====
|
||||
TODO
|
||||
====
|
||||
|
|
@ -48,7 +48,8 @@ if isinstance(TMP_STORAGE_CLASS, six.string_types):
|
|||
msg = "Could not import '%s' for import_export setting 'IMPORT_EXPORT_TMP_STORAGE_CLASS'" % TMP_STORAGE_CLASS
|
||||
raise ImportError(msg)
|
||||
|
||||
#: import / export formats
|
||||
#: These are the default formats for import and export. Whether they can be
|
||||
#: used or not is depending on their implementation in the tablib library.
|
||||
DEFAULT_FORMATS = (
|
||||
base_formats.CSV,
|
||||
base_formats.XLS,
|
||||
|
|
|
@ -64,47 +64,65 @@ class ResourceOptions(object):
|
|||
"""
|
||||
The inner Meta class allows for class-level configuration of how the
|
||||
Resource should behave. The following options are available:
|
||||
"""
|
||||
|
||||
* ``fields`` - Controls what introspected fields the Resource
|
||||
should include. A whitelist of fields.
|
||||
|
||||
* ``exclude`` - Controls what introspected fields the Resource should
|
||||
NOT include. A blacklist of fields.
|
||||
|
||||
* ``model`` - Django Model class. It is used to introspect available
|
||||
fields.
|
||||
|
||||
* ``instance_loader_class`` - Controls which class instance will take
|
||||
care of loading existing objects.
|
||||
|
||||
* ``import_id_fields`` - Controls which object fields will be used to
|
||||
identify existing instances.
|
||||
|
||||
* ``export_order`` - Controls export order for columns.
|
||||
|
||||
* ``widgets`` - dictionary defines widget kwargs for fields.
|
||||
|
||||
* ``use_transactions`` - Controls if import should use database
|
||||
transactions. Default value is ``None`` meaning
|
||||
``settings.IMPORT_EXPORT_USE_TRANSACTIONS`` will be evaluated.
|
||||
|
||||
* ``skip_unchanged`` - Controls if the import should skip unchanged
|
||||
records. Default value is False
|
||||
|
||||
* ``report_skipped`` - Controls if the result reports skipped rows
|
||||
Default value is True
|
||||
model = None
|
||||
"""
|
||||
Django Model class. It is used to introspect available
|
||||
fields.
|
||||
|
||||
"""
|
||||
fields = None
|
||||
model = None
|
||||
"""
|
||||
Controls what introspected fields the Resource should include. A whitelist
|
||||
of fields.
|
||||
"""
|
||||
|
||||
exclude = None
|
||||
"""
|
||||
Controls what introspected fields the Resource should
|
||||
NOT include. A blacklist of fields.
|
||||
"""
|
||||
|
||||
instance_loader_class = None
|
||||
"""
|
||||
Controls which class instance will take
|
||||
care of loading existing objects.
|
||||
"""
|
||||
|
||||
import_id_fields = ['id']
|
||||
"""
|
||||
Controls which object fields will be used to
|
||||
identify existing instances.
|
||||
"""
|
||||
|
||||
export_order = None
|
||||
"""
|
||||
Controls export order for columns.
|
||||
"""
|
||||
|
||||
widgets = None
|
||||
"""
|
||||
This dictionary defines widget kwargs for fields.
|
||||
"""
|
||||
|
||||
use_transactions = None
|
||||
"""
|
||||
Controls if import should use database transactions. Default value is
|
||||
``None`` meaning ``settings.IMPORT_EXPORT_USE_TRANSACTIONS`` will be
|
||||
evaluated.
|
||||
"""
|
||||
|
||||
skip_unchanged = False
|
||||
"""
|
||||
Controls if the import should skip unchanged records. Default value is
|
||||
False
|
||||
"""
|
||||
|
||||
report_skipped = True
|
||||
"""
|
||||
Controls if the result reports skipped rows Default value is True
|
||||
"""
|
||||
|
||||
|
||||
class DeclarativeMetaclass(type):
|
||||
|
@ -161,14 +179,15 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
|
||||
def get_fields(self):
|
||||
"""
|
||||
Returns fields in ``export_order`` order.
|
||||
Returns fields sorted according to
|
||||
:attr:`~import_export.resources.ResourceOptions.export_order`.
|
||||
"""
|
||||
return [self.fields[f] for f in self.get_export_order()]
|
||||
|
||||
@classmethod
|
||||
def get_field_name(cls, field):
|
||||
"""
|
||||
Returns field name for given field.
|
||||
Returns the field name for a given field.
|
||||
"""
|
||||
for field_name, f in cls.fields.items():
|
||||
if f == field:
|
||||
|
@ -180,9 +199,15 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
raise NotImplementedError()
|
||||
|
||||
def get_instance(self, instance_loader, row):
|
||||
"""
|
||||
Calls the :doc:`InstanceLoader <api_instance_loaders>`.
|
||||
"""
|
||||
return instance_loader.get_instance(row)
|
||||
|
||||
def get_or_init_instance(self, instance_loader, row):
|
||||
"""
|
||||
Either fetches an already existing instance or initializes a new one.
|
||||
"""
|
||||
instance = self.get_instance(instance_loader, row)
|
||||
if instance:
|
||||
return (instance, False)
|
||||
|
@ -190,6 +215,12 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
return (self.init_instance(row), True)
|
||||
|
||||
def save_instance(self, instance, dry_run=False):
|
||||
"""
|
||||
Takes care of saving the object to the database.
|
||||
|
||||
Keep in mind that this is done by calling ``instance.save()``, so
|
||||
objects are not created in bulk!
|
||||
"""
|
||||
self.before_save_instance(instance, dry_run)
|
||||
if not dry_run:
|
||||
instance.save()
|
||||
|
@ -197,17 +228,20 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
|
||||
def before_save_instance(self, instance, dry_run):
|
||||
"""
|
||||
Override to add additional logic.
|
||||
Override to add additional logic. Does nothing by default.
|
||||
"""
|
||||
pass
|
||||
|
||||
def after_save_instance(self, instance, dry_run):
|
||||
"""
|
||||
Override to add additional logic.
|
||||
Override to add additional logic. Does nothing by default.
|
||||
"""
|
||||
pass
|
||||
|
||||
def delete_instance(self, instance, dry_run=False):
|
||||
"""
|
||||
Calls :meth:`instance.delete` as long as ``dry_run`` is not set.
|
||||
"""
|
||||
self.before_delete_instance(instance, dry_run)
|
||||
if not dry_run:
|
||||
instance.delete()
|
||||
|
@ -215,22 +249,28 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
|
||||
def before_delete_instance(self, instance, dry_run):
|
||||
"""
|
||||
Override to add additional logic.
|
||||
Override to add additional logic. Does nothing by default.
|
||||
"""
|
||||
pass
|
||||
|
||||
def after_delete_instance(self, instance, dry_run):
|
||||
"""
|
||||
Override to add additional logic.
|
||||
Override to add additional logic. Does nothing by default.
|
||||
"""
|
||||
pass
|
||||
|
||||
def import_field(self, field, obj, data):
|
||||
"""
|
||||
Calls :meth:`import_export.fields.Field.save` if ``Field.attribute``
|
||||
and ``Field.column_name`` are found in ``data``.
|
||||
"""
|
||||
if field.attribute and field.column_name in data:
|
||||
field.save(obj, data)
|
||||
|
||||
def import_obj(self, obj, data, dry_run):
|
||||
"""
|
||||
Traverses every field in this Resource and calls
|
||||
:meth:`~import_export.resources.Resource.import_field`.
|
||||
"""
|
||||
for field in self.get_fields():
|
||||
if isinstance(field.widget, widgets.ManyToManyWidget):
|
||||
|
@ -308,7 +348,12 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
|
||||
def before_import(self, dataset, dry_run, **kwargs):
|
||||
"""
|
||||
Override to add additional logic.
|
||||
Override to add additional logic. Does nothing by default.
|
||||
|
||||
This method receives the ``dataset`` that's going to be imported, the
|
||||
``dry_run`` parameter which determines whether changes are saved to
|
||||
the database, and any additional keyword arguments passed to
|
||||
``import_data`` in a ``kwargs`` dict.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -316,12 +361,19 @@ class Resource(six.with_metaclass(DeclarativeMetaclass)):
|
|||
def import_data(self, dataset, dry_run=False, raise_errors=False,
|
||||
use_transactions=None, **kwargs):
|
||||
"""
|
||||
Imports data from ``dataset``.
|
||||
Imports data from ``tablib.Dataset``. Refer to :doc:`import_workflow`
|
||||
for a more complete description of the whole import process.
|
||||
|
||||
``use_transactions``
|
||||
If ``True`` import process will be processed inside transaction.
|
||||
If ``dry_run`` is set, or error occurs, transaction will be rolled
|
||||
back.
|
||||
:param dataset: A ``tablib.Dataset``
|
||||
|
||||
:param raise_errors: Whether errors should be printed to the end user
|
||||
or raised regularly.
|
||||
|
||||
:param use_transactions: If ``True`` import process will be processed
|
||||
inside transaction.
|
||||
|
||||
:param dry_run: If ``dry_run`` is set, or error occurs, transaction
|
||||
will be rolled back.
|
||||
"""
|
||||
result = Result()
|
||||
result.diff_headers = self.get_diff_headers()
|
||||
|
@ -600,12 +652,21 @@ class ModelResource(six.with_metaclass(ModelDeclarativeMetaclass, Resource)):
|
|||
return field
|
||||
|
||||
def get_import_id_fields(self):
|
||||
"""
|
||||
"""
|
||||
return self._meta.import_id_fields
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Returns a queryset of all objects for this model. Override this if you
|
||||
want to limit the returned queryset.
|
||||
"""
|
||||
return self._meta.model.objects.all()
|
||||
|
||||
def init_instance(self, row=None):
|
||||
"""
|
||||
Initializes a new Django model.
|
||||
"""
|
||||
return self._meta.model()
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal
|
||||
|
@ -14,29 +15,40 @@ except ImportError:
|
|||
|
||||
class Widget(object):
|
||||
"""
|
||||
Widget takes care of converting between import and export representations.
|
||||
A Widget takes care of converting between import and export representations.
|
||||
|
||||
Widget objects have two functions:
|
||||
|
||||
* converts object field value to export representation
|
||||
|
||||
* converts import value and converts it to appropriate python
|
||||
representation
|
||||
This is achieved by the two methods,
|
||||
:meth:`~import_export.widgets.Widget.clean` and
|
||||
:meth:`~import_export.widgets.Widget.render`.
|
||||
"""
|
||||
|
||||
def clean(self, value):
|
||||
"""
|
||||
Returns appropriate python objects for import value.
|
||||
Returns an appropriate Python object for an imported value.
|
||||
|
||||
For example, if you import a value from a spreadsheet,
|
||||
:meth:`~import_export.widgets.Widget.clean` handles conversion
|
||||
of this value into the corresponding Python object.
|
||||
|
||||
Numbers or dates can be *cleaned* to their respective data types and
|
||||
don't have to be imported as Strings.
|
||||
"""
|
||||
return value
|
||||
|
||||
def render(self, value):
|
||||
"""
|
||||
Returns export representation of python value.
|
||||
Returns an export representation of a Python value.
|
||||
|
||||
For example, if you have an object you want to export,
|
||||
:meth:`~import_export.widgets.Widget.render` takes care of converting
|
||||
the object's field to a value that can be written to a spreadsheet.
|
||||
"""
|
||||
return force_text(value)
|
||||
|
||||
|
||||
class NumberWidget(Widget):
|
||||
"""
|
||||
"""
|
||||
|
||||
def is_empty(self, value):
|
||||
# 0 is not empty
|
||||
|
@ -135,7 +147,8 @@ class DateTimeWidget(Widget):
|
|||
"""
|
||||
Widget for converting date fields.
|
||||
|
||||
Takes optional ``format`` parameter.
|
||||
Takes optional ``format`` parameter. If none is set, either
|
||||
``settings.DATETIME_INPUT_FORMATS`` or ``"%Y-%m-%d %H:%M:%S"`` is used.
|
||||
"""
|
||||
|
||||
def __init__(self, format=None):
|
||||
|
@ -205,25 +218,38 @@ class TimeWidget(Widget):
|
|||
|
||||
class ForeignKeyWidget(Widget):
|
||||
"""
|
||||
Widget for ``ForeignKey`` which looks up a related model.
|
||||
Widget for a ``ForeignKey`` field which looks up a related model using
|
||||
"natural keys" in both export an import.
|
||||
|
||||
The lookup field defaults to using the primary key (``pk``), but
|
||||
can be customised to use any field on the related model.
|
||||
The lookup field defaults to using the primary key (``pk``) as lookup
|
||||
criterion but can be customised to use any field on the related model.
|
||||
|
||||
e.g. To use a lookup field other than ``pk``, rather than specifying a
|
||||
field in your Resource as ``class Meta: fields = ('author__name', ...)``,
|
||||
you would specify it in your Resource like so:
|
||||
Unlike specifying a related field in your resource like so…
|
||||
|
||||
::
|
||||
|
||||
class Meta:
|
||||
fields = ('author__name',)
|
||||
|
||||
…using a :class:`~import_export.widgets.ForeignKeyWidget` has the
|
||||
advantage that it can not only be used for exporting, but also importing
|
||||
data with foreign key relationships.
|
||||
|
||||
Here's an example on how to use
|
||||
:class:`~import_export.widgets.ForeignKeyWidget` to lookup related objects
|
||||
using ``Author.name`` instead of ``Author.pk``::
|
||||
|
||||
class BookResource(resources.ModelResource):
|
||||
author = fields.Field(column_name='author', attribute='author', \
|
||||
author = fields.Field(
|
||||
column_name='author',
|
||||
attribute='author',
|
||||
widget=ForeignKeyWidget(Author, 'name'))
|
||||
class Meta: fields = ('author', ...)
|
||||
|
||||
This will allow you to use "natural keys" for both import and export.
|
||||
class Meta:
|
||||
fields = ('author',)
|
||||
|
||||
Parameters:
|
||||
``model`` should be the Model instance for this ForeignKey (required).
|
||||
``field`` should be the lookup field on the related model.
|
||||
:param model: The Model the ForeignKey refers to (required).
|
||||
:param field: A field on the related model used for looking up a particular object.
|
||||
"""
|
||||
def __init__(self, model, field='pk', *args, **kwargs):
|
||||
self.model = model
|
||||
|
@ -242,16 +268,12 @@ class ForeignKeyWidget(Widget):
|
|||
|
||||
class ManyToManyWidget(Widget):
|
||||
"""
|
||||
Widget for ``ManyToManyField`` model field that represent m2m field
|
||||
as values that identify many-to-many relationship.
|
||||
Widget that converts between representations of a ManyToMany relationships
|
||||
as a list and an actual ManyToMany field.
|
||||
|
||||
Requires a positional argument: the class to which the field is related.
|
||||
|
||||
Optional keyword arguments are:
|
||||
|
||||
separator - default ","
|
||||
|
||||
field - field of related model, default ``pk``
|
||||
:param model: The model the ManyToMany field refers to (required).
|
||||
:param separator: Defaults to ``','``.
|
||||
:param field: A field on the related model. Default is ``pk``.
|
||||
"""
|
||||
|
||||
def __init__(self, model, separator=None, field=None, *args, **kwargs):
|
||||
|
|
Loading…
Reference in New Issue