I have a small Django application with cities_light
, smart_selects
, and import_export
. I am able to properly export from the admin site into csv files using resources
and ForeignKeyWidget
from import_export
. However, there is something wrong with my import and I'm not sure how to fix it. When trying to import through the admin site from the same exported csv file, I get jobs.models.City.MultipleObjectsReturned: get() returned more than one City -- it returned 2!
.
This question is similar to this one but I still do not understand what is wrong. Would greatly appreciate if the responses contain links to the documentation that explain what I am doing wrong, since I've gone through ReadTheDocs but have been unsuccessful at finding my mistake.
Thanks.
models.py
class Country(AbstractCountry):
pass
connect_default_signals(Country)
class Region(AbstractRegion):
def __str__(self):
return self.name
country = models.ForeignKey(Country, on_delete=models.PROTECT)
connect_default_signals(Region)
class SubRegion(AbstractSubRegion):
pass
connect_default_signals(SubRegion)
class City(AbstractCity):
def __str__(self):
return self.name
region = models.ForeignKey(Region, on_delete=models.PROTECT)
connect_default_signals(City)
class JobPost(models.Model):
title = models.CharField(max_length=100)
company = models.CharField(max_length=100)
urlCompany = models.URLField(blank=True)
urlApplication = models.URLField(blank=True)
contactEmail = models.EmailField(max_length=100, blank=True)
jobType = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Country, on_delete=models.PROTECT, blank=True)
region = ChainedForeignKey(
Region,
chained_field="country",
chained_model_field="country",
show_all=False,
auto_choose=True,
sort=True,
blank=True
)
city = ChainedForeignKey(
City,
chained_field="region",
chained_model_field="region",
show_all=False,
auto_choose=True,
sort=True,
blank=True
)
createdAt = models.DateTimeField(auto_now_add=True)
remainingDays = models.IntegerField(default=90, blank=False)
valid = models.BooleanField(default=True)
visible = models.BooleanField(default=True)
description = models.TextField()
admin.py
from .models import JobPost, Country, Region, City
class JobPostResource(resources.ModelResource):
country = fields.Field(
column_name='country',
attribute='country',
widget=ForeignKeyWidget(Country, 'name'))
region = fields.Field(
column_name='region',
attribute='region',
widget=ForeignKeyWidget(Region, 'name'))
city = fields.Field(
column_name='city',
attribute='city',
widget=ForeignKeyWidget(City, 'name'))
class Meta:
model = JobPost
export_order = ('id', 'title', 'company', 'urlCompany', 'urlApplication', 'contactEmail',
'jobType', 'country', 'region', 'city', 'createdAt', 'remainingDays',
'valid', 'visible', 'description')
fields = ('id', 'title', 'company', 'urlCompany', 'urlApplication', 'contactEmail',
'jobType', 'country', 'region', 'city', 'createdAt', 'remainingDays',
'valid', 'visible', 'description')
class JobPostAdmin(SummernoteModelAdmin, ImportExportModelAdmin):
summernote_fields = ('description', )
resource_class = JobPostResource
admin.site.register(JobPost, JobPostAdmin)
csv
id,title,company,urlCompany,urlApplication,contactEmail,jobType,country,region,city,createdAt,remainingDays,valid,visible,description
888,xxx,asfasdf,,,,,United States,Texas,Austin,2022-06-10 19:27:36,90,1,1,<p>asdfasdf</p>
Came back some months later to answer my own question...
In widgets.py
, in the source code of import_export
, when I searched for get_queryset
to try to understand a bit better what was going on I found this comment:
Overwrite this method if you want to limit the pool of objects from
which the related object is retrieved.
:param value: The field's value in the datasource.
:param row: The datasource's current row.
As an example; if you'd like to have ForeignKeyWidget look up a Person
by their pre- **and** lastname column, you could subclass the widget
like so::
class FullNameForeignKeyWidget(ForeignKeyWidget):
def get_queryset(self, value, row, *args, **kwargs):
return self.model.objects.filter(
first_name__iexact=row["first_name"],
last_name__iexact=row["last_name"]
)
This is exactly what I did, I overwrote the method. For example I now have:
class RegionCountryForeignKeyWidget(ForeignKeyWidget):
def get_queryset(self, value, row, *args, **kwargs):
return self.model.objects.filter(
name__iexact=row["region"],
country__name__iexact=row["country"]
)
and it works.