Search code examples
pythondjangodjango-import-export

How do I order the import fields in admin using django import-export?


I am using django import-export and ImportExportModelAdmin to import data from a file to the database from the admin interface.

Below is the model resource i use:

class ImportedBetResource(resources.ModelResource):
    date = fields.Field(column_name='Date',
                         attribute='date',
                         widget=DateWidget(format="%d/%m/%Y"))
    time = fields.Field(column_name='Time',
                         attribute='time',
                         widget=TimeWidget(format="%H:%M"))
    sport = fields.Field(column_name='Sport',
                         attribute='sport',
                         widget=ForeignKeyWidget(Sport, 'name'))
    country = fields.Field(column_name='Country',
                           attribute='country',
                           widget=ForeignKeyWidget(Country, 'name'))
    bookie = fields.Field(column_name='Bookie',
                          attribute='bookie',
                          widget=ForeignKeyWidget(Bookie, 'name'))
    currency = fields.Field(column_name='Currency',
                            attribute='stake_currency',
                            widget=ForeignKeyWidget(Currency, 'name'))
    odds = fields.Field(column_name="Odds",
                        attribute="odds",
                        widget=DecimalWidget())
    status = fields.Field(column_name='Status',
                          attribute='status',
                          widget=ForeignKeyWidget(Status, 'name'))

    class Meta:
        model = Bet
        fields = ("id", "date", "time", "sport",
                  "country",
                  "competition", "home",
                  "visitor",
                  "bookie", "bet", "stake",
                  "currency",
                  "odds", "status")
        clean_model_instances = True


    @classmethod
    def field_from_django_field(self, field_name, django_field, readonly):
        """
        Returns a Resource Field instance for the given Django model field.
        """
        FieldWidget = self.widget_from_django_field(django_field)
        widget_kwargs = self.widget_kwargs_for_field(field_name)
        field = fields.Field(attribute=field_name, column_name=field_name.replace("__name", "").title(),
                             widget=FieldWidget(**widget_kwargs), readonly=readonly)
        return field

This is a screenshot of the import view from the documentation:

enter image description here

You can see the text "This importer will import the following fields" followed by the fields names.

In my case, the explicitly defined fields appear first, ex.:

country = fields.Field(column_name='Country',
                       attribute='country',
                       widget=ForeignKeyWidget(Country, 'name'))

and then the remaining fields defined in class Meta fields:

 This importer will import the following fields: Date, Time, Sport, Country, Bookie, Currency, Odds, Status, Id, Competition, Home, Visitor, Bet, Stake

The issue is that the order of fields don't follow the order of fields in my file and the data get scrambled.

This only happens when there are errors in the file.


Solution

  • export_order is the answer!

    Add the export_order option to your resources' Meta field like so:

    class Meta:
            model = Bet
            fields = ("id", "date", "time", "sport",
                      "country",
                      "competition", "home",
                      "visitor",
                      "bookie", "bet", "stake",
                      "currency",
                      "odds", "status")
            clean_model_instances = True
            export_order = ["Date", "Time", "Sport", "Country", 
                           "Bookie", "Currency", "Odds", "Status", 
                           "Id", "Competition", "Home", "Visitor", 
                           "Bet", "Stake"]
    

    (or in whatever preferred order you would like) and django-import-export will import your stated fields accordingly! The reason why this works is that export_order is called by get_fields() which in turn is called by get_import_fields() as well as get_export_fields() affecting the entire workflows of both processes.

    def export_order = None
    def get_fields(self, **kwargs):
            """
            Returns fields sorted according to
            :attr:`~import_export.resources.ResourceOptions.export_order`.
            """
            return [self.fields[f] for f in self.get_export_order()]
    
    def get_import_fields(self):
            return self.get_fields()
    def get_export_fields(self):
            return self.get_fields()
    
    def import_obj(self, obj, data, dry_run):
            """
            Traverses every field in this Resource and calls
            :meth:`~import_export.resources.Resource.import_field`. If
            ``import_field()`` results in a ``ValueError`` being raised for
            one of more fields, those errors are captured and reraised as a single,
            multi-field ValidationError."""
            errors = {}
            for field in self.get_import_fields():
                if isinstance(field.widget, widgets.ManyToManyWidget):
                    continue
                try:
                    self.import_field(field, obj, data)
                except ValueError as e:
                    errors[field.attribute] = ValidationError(
                        force_text(e), code="invalid")
            if errors:
                raise ValidationError(errors)
    

    Above are excerpts from import_export.resources.py. For further clarification, I would also recommend the import data workflow documentation and methods for import_export's resources.

    I hope this can be of help!