Соответствие с Django import_export с несколькими полями
Я хочу импортировать CSV в Django. Проблема возникает при попытке импорта на основе атрибутов. Вот мой код:
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"
Теперь я создаю ресурс, который должен найти нужные объекты, но, похоже, он вообще не входит в ForeignKeyWidget(s):
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
Я добавил несколько логгеров, но распечатываю лог только в AmountForeignKeyWidget. Главный вопрос: Как искать объекты в Space по атрибутам (space_code,space_type,space_date) и в Time искать и создавать по (time_date,time_type).
Меньший вопрос - почему не используются SpaceForeignKeyWidget и TimeForeignKeyWidget?
Основной вопрос: Как искать объекты в
.Spaceпо атрибутам(space_code,space_type,space_date)и вTimeискать и создавать по(time_date,time_type)
Похоже, что вы правильно ищете эти объекты, но, возможно, вызов не происходит. Часто при использовании import-export вы сэкономите себе много времени, если настроите отладчик и пройдете по коду.
Возможно, в вашем исходном csv нет столбца 'пробел' или 'время'. Если таких полей нет, то процесс импорта будет молча пропускать это объявление. Если вам нужно создать объекты, если они не существуют, то, вероятно, лучше использовать before_import_row() для этого.
Меньший вопрос - почему не используются
SpaceForeignKeyWidgetиTimeForeignKeyWidget?
Как и выше, если в исходных данных нет столбцов 'space' или 'time', то они никогда не будут вызваны.
Не должно иметь значения, но ваша декларация метода clean() не определяет row как kwarg в SpaceForeignKeyWidget и TimeForeignKeyWidget. Измените определение clean() на:
def clean(self, value, row=None, **kwargs):
# your implementation here
Я не вижу, что это исправит проблему, но, возможно, при запуске в вашем контексте это является проблемой.
Обратите внимание, что есть некоторые изменения, которые вы можете сделать для улучшения вашего кода.
Для AmountForeignKeyWidget, если вам нужно искать только по одному значению, вы можете изменить объявление ресурса следующим образом:
class EventResource(ModelResource):
amount = Field(
column_name="amount",
attribute="amount",
widget=ForeignKeyWidget(Amount, field="name__iexact"),
)
Вам не нужно никакой дополнительной логики, и поиск будет нечувствителен к регистру.
Мне удалось решить все проблемы и сделать правильный импорт. Ниже приведен код, который я использовал:
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
Для поля amount мне не нужно делать производный виджет, так как он использует только одну переменную в CSV. Для двух других реализация последует. Я заметил, что виджеты для двух других переменных не вызывались, и причина в том, что имена переменных не существовали в моем CSV-файле. Когда я переименовал их в имена столбцов, существующих в CSV, они были вызваны.
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 только ищет существующую запись и возвращает объект, а TimeForeignKeyWidget создает несуществующую и возвращает запись. Таким образом, нет необходимости использовать before_import_row() и вся логика локализована на этих двух виджетах.