Соответствие с 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()
и вся логика локализована на этих двух виджетах.