Django-import-export post_save called twice
Asked Answered
S

3

5

I created a custom user subclassed from AbstractUser and a post_save signal and a receiver that prints the new user's id.

@receiver(post_save, sender=CustomUser, dispatch_uid='members.models.customuser.post_save')
def post_save_custom_user(sender, instance=None, created=False, **kwargs):         
    if not created:                                                                
        return                                                                     
    print('post_save_custom_user: {}'.format(instance.id))

When I create a new user via the admin interface the receiver is called once. When I import a user using django-import-export the receiver is called twice: once after the initial Submit of the import file and then again after the Confirm Import. Browsing through the code I see it creates the user in dry_run, rolls back the transaction and creates it again. But how can I tell in my receiver if it's a dry run or not?

I am using Python 3.6, Django 3.0.3, django-import-export 2.0.1

Supervise answered 8/2, 2020 at 6:50 Comment(3)
try wrapping the print function in transaction.on_commit. you can import transaction from django.dbDapplegray
Thanks @mattyx17. I added transaction.on_commit(lambda: print('post_save_custom_user: {}'.format(instance.id))) but it still gets called twice.Supervise
I got the same behavior for Django version 3.0.5 and Python 3.7: [10/May/2020 12:36:34] "POST /admin/listing/category/import/ HTTP/1.1" 200 3860 [10/May/2020 12:36:39] "POST /admin/listing/category/process_import/ HTTP/1.1" 302 0 POST are done twice why is that?Teriann
L
8

It appears that django-import-export triggers the post_save on an import confirmation and then again after the import. Recommendations to use on_commit didn't work for me, hence I had to stop using the signals. There's ModelResource.after_save_instace method though:

class MyResource(ModelResource):

    class Meta:
        model = MyModel
    
    def after_save_instance(
        self, instance: MyModel, using_transactions: bool, dry_run: bool,
    ):
        super().after_save_instance(instance, using_transactions, dry_run)
        if dry_run is False:
            my_model_on_save_action(instance)
Lakeesha answered 19/5, 2020 at 22:0 Comment(0)
M
2

I have fixed this issue by using the "dry-run" flag.

My Resource class is like:

class SentenceResource(resources.ModelResource):

    def before_save_instance(self, instance, using_transactions, dry_run):
        instance.dry_run = dry_run # set a temporal flag for dry-run mode

    class Meta:
        model = Sentence

And my signal is like:

@receiver(post_save, sender=Sentence)
def create_sentence(sender, instance=None, created=False, **kwargs):
    if hasattr(instance, 'dry_run'):
        if instance.dry_run:
            return # not send to elastic search
        else:
            pass
    if created:
        ...
    else:
        ...
Mezzo answered 26/3, 2022 at 3:42 Comment(0)
L
0

Thanks for this. Does anyone know why Django-import-export calls the save() method twice for a model during an import with confirm using dry_run = True ? As far as I can see the import data is only temp saved to a temp location in memory or disk and is not actually written to the DB.

To get my solution working I needed to use the solution above using after_save_instance on the Model Resource but... For single instance additions using a CreateView I needed to adjust my form_valid method to carry out an action when a new instance of the model is created:

class MyCreateView(CreateView): 
    model = MyModel
    fields = [ 'email', 'name']

    def form_valid(self, form):
        self.object = form.save()
        self.object.save() 
        ## Perform action here like send email etc.
        return HttpResponseRedirect(self.get_success_url()) 
Limner answered 7/7, 2020 at 16:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.