pythondjangodjango-import-export

Match with Django import_export with multiple fields


I would like to import a CSV in Django. The issue occurs when trying to import based on the attributes. Here is my code:

class Event(models.Model):
    id = models.BigAutoField(primary_key=True)
    amount = models.ForeignKey(Amount, on_delete=models.CASCADE)
    value = models.FloatField()
    space = models.ForeignKey(Space, on_delete=models.RESTRICT)
    time = models.ForeignKey(Time, on_delete=models.RESTRICT)

    class Meta:
        managed = True
        db_table = "event"

class Space(models.Model):
    objects = SpaceManager()

    id = models.BigAutoField(primary_key=True)
    code = models.CharField(max_length=100)
    type = models.ForeignKey(SpaceType, on_delete=models.RESTRICT)
    space_date = models.DateField(blank=True, null=True)

    def natural_key(self):
        return self.code  # + self.type + self.source_date

    def __str__(self):
        return f"{self.name}"

    class Meta:
        managed = True
        db_table = "space"

class Time(models.Model):
    objects = TimeManager()

    id = models.BigAutoField(primary_key=True)
    type = models.ForeignKey(TimeType, on_delete=models.RESTRICT)
    startdate = models.DateTimeField()
    enddate = models.DateTimeField()

    def natural_key(self):
        return self.name

    def __str__(self):
        return f"{self.name}"

    class Meta:
        managed = True
        db_table = "time"

Now, I create the resource that should find the right objects, but it seems it does not enter into ForeignKeyWidget(s) at all:

class AmountForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row=None, **kwargs):
        logger.critical("<<<<< {AmountForeignKeyWidget} <<<<<<<")
        name_upper = value.upper()
        amount = Amount.objects.get_by_natural_key(name=name_upper)
        return amount


class SpaceForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row, **kwargs):
        logger.critical("<<<<< {SpaceForeignKeyWidget} <<<<<<<")
        space_code = row["space_code"]
        space_type = SpatialDimensionType.objects.get_by_natural_key(row["space_type"])
        try:
            space_date = datetime.strptime(row["space_date"], "%Y%m%d")
        except ValueError:
            space_date = None
        space = Space.objects.get(
            code=space_code, type=space_type, source_date=space_date
        )
        return space


class TimeForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row, **kwargs):
        logger.critical("<<<<< {TimeForeignKeyWidget} <<<<<<<")
        time_type = TimeType.objects.get_by_natural_key(row["time_type"])
        time_date = parse_datetime(row["time_date"])
        time = Time.objects.get_or_create(
            type=time_type, startdate=time_date), defaults={...}
        )
        return time


class EventResource(ModelResource):
    amount = Field(
        column_name="amount",
        attribute="amount",
        widget=AmountForeignKeyWidget(Amount),
    )
    space = Field(
        # column_name="space_code",
        attribute="space",
        widget=SpaceForeignKeyWidget(Space),
    )
    time = Field(
        attribute="time",
        widget=TimeForeignKeyWidget(Time),
    )

    def before_import_row(self, row, row_number=None, **kwargs):
        logger.error(f">>>> before_import_row() >>>>>>")
        time_date = datetime.strptime(row["time_date"], "%Y%m%d").date()
        time_type = TimeType.objects.get_by_natural_key(row["time_type"])
        Time.objects.get_or_create(
            type=time_type, startdate=time_date,
            defaults={
                "name": str(time_type) + str(time_date),
                "type": time_type,
                "startdate": time_date,
                "enddate": time_date + timedelta(days=1),
            },
        )

    class Meta:
        model = Event

I added some loggers, but I only print out the log at AmountForeignKeyWidget. The main question is: How to search for objects in Space by attributes (space_code,space_type,space_date) and in Time search and create by (time_date,time_type) A lesser question is why SpaceForeignKeyWidget and TimeForeignKeyWidget are not used?


Solution

  • I managed to solve all the issues and make proper imports. Following is the code I used:

    class EventResource(ModelResource):
        amount = Field(
            column_name="amount",
            attribute="amount",
            widget=ForeignKeyWidget(Amount, field="name__iexact"),
        )
        space_code = Field(
            attribute="space",
            widget=SpaceForeignKeyWidget(Space),
        )
        time_date = Field(
            attribute="time",
            widget=TimeForeignKeyWidget(Time),
        )
    
        class Meta:
            model = Event
    

    For the amount field I don't need to make a derivative Widget, since it is using only one variable in CSV. For the two others, implementation follows. I noticed that the widgets for the two other variables were not called and the reason is the variable names were non-existent in my CSV file. When I renamed them to the column names existing in the CSV they have been called.

    class SpaceForeignKeyWidget(ForeignKeyWidget):
        def clean(self, value, row, **kwargs):
            space_code = row["spacial_code"]
            space_type = SpaceDimensionType.objects.get(type=row["space_type"])
            try:
                space_date = datetime.strptime(row["space_date"], "%Y%m%d")
            except ValueError:
                space_date = None
    
            space = SpaceDimension.objects.get(
                code=space_code, type=space_type, source_date=space_date
            )
            return space
    
    
    class TimeForeignKeyWidget(ForeignKeyWidget):
        def clean(self, value, row, **kwargs):
            time_type = TimeDimensionType.objects.get(type=row["time_type"])
            delta = T_TYPES[time_type]
    
            start_date = datetime.strptime(row["time_date"], "%Y%m%d").date()
            end_date = start_date + timedelta(days=delta)
            time, created = TimeDimension.objects.get_or_create(
                type=time_type,
                startdate=start_date,
                enddate=start_date + timedelta(days=delta),
                defaults={
                    "name": f"{time_type}: {start_date}-{end_date}",
                    "type": time_type,
                    "startdate": start_date,
                    "enddate": end_date,
                },
            )
            return temporal
    
    

    SpaceForeignKeyWidget only searches it the record is existing and returns the object and TimeForeignKeyWidget creates if non-existing and returns the record. This way no need to use before_import_row() and all the logic is localized to this two widgets.