Search code examples
djangodjango-import-export

Create object if not exists in Django ImportExportModelAdmin


I have these two models:

Profile_model.py

class Profile(models.Model):
    firstname = models.CharField(max_length=200, blank=False)
    lastname = models.CharField(max_length=200, blank=False)
    email = models.CharField(max_length=200, unique=True, blank=False)
    ...

Investment_model.py

class Investment(models.Model):
    invested = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
    profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
    ...

and I have this admin:

Investment_admin.py

class InvestmentResource(resources.ModelResource):
    ...
    firstname = fields.Field(attribute='profile', 
        widget=ForeignKeyWidget(Profile, field='firstname'), 
        column_name='firstname')
    lastname = fields.Field(attribute='profile', 
        widget=ForeignKeyWidget(Profile, field='lastname'), 
        column_name='lastname')
    email = fields.Field(attribute='email', 
        widget=ForeignKeyWidget(Profile, field='email'), 
        column_name='email')
    class Meta:
        model = Investment
        fields = (
            'firstname',
            'lastname',
            'email',
            'invested',)
        
        export_order = fields


class InvestmentAdmin(ImportExportModelAdmin, admin.ModelAdmin):
        ...
        resource_class = InvestmentResource
        ...

I am using django's ImportExportModelAdmin for bulk imports and exports but when I try to import, I get this error:

enter image description here

I get that its producing this error because the profile hasn't been created yet. But what do I have to do to implement an update_or_create inside the ImportExportModelAdmin?


Solution

  • Option 1 is to use before_import() to scan through the dataset and create Profiles in batch if they do not exist already.

    Option 2 is to override methods and create the profiles just before the Investment row is imported. This is only necessary for new Investment objects. This assumes that 'email' will uniquely identify a Profile, you will need to adjust this if not.

    Note that firstname and lastname can be set on the Profile object before it is created.

    class InvestmentResource(resources.ModelResource):
        firstname = fields.Field(attribute='profile__firstname', 
            widget=CharWidget(), column_name='firstname')
    
        lastname = fields.Field(attribute='profile__lastname', 
            widget=CharWidget(), column_name='lastname')
    
        email = fields.Field(attribute='email', 
            widget=ForeignKeyWidget(Profile, field='email'), 
            column_name='email')
    
        def before_import_row(self, row, row_number=None, **kwargs):
            self.email = row["email"]
    
        def after_import_instance(self, instance, new, row_number=None, **kwargs):
            """
            Create any missing Profile entries prior to importing rows.
            """
            if (
                new
                and not Profile.objects.filter(
                    name=self.email
                ).exists()
            ):
                obj, created = Profile.objects.get_or_create(
                    name=self.email
                )
                if created:
                    logger.debug(f"no Profile in db with name='{self.email}' - created")
                    instance.profile = obj
    

    Obviously the Profile creation will be a side-effect of an import, so you may need to consider using transactions if you don't want Profiles created if the import fails.