Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

During import, post_save receiver is called twice #1078

Open
mikejmets opened this issue Feb 9, 2020 · 9 comments
Open

During import, post_save receiver is called twice #1078

mikejmets opened this issue Feb 9, 2020 · 9 comments
Labels
hacktoberfest OSS free-for-all

Comments

@mikejmets
Copy link

mikejmets commented Feb 9, 2020

I asked this question on SO but have had no reply so I thought I raise an issue: https://stackoverflow.com/questions/60124600/django-import-export-post-save-called-twice

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

@SPineapple
Copy link

I am having the same issue,
I figured maybe 'post_import' might be helpful but I don't know how to access the created data so I can do extra things with it.
Also it is called only once after import confirmation.

from import_export.signals import post_import, post_export

@receiver(post_import, dispatch_uid='balabala...')
def _post_import(model, instance, **kwargs):
    # model is the actual model instance which after import
    pass

@mikejmets
Copy link
Author

I have also resorted to using post_import. To determine newly imported data from existing data I added a status field to the model with a default of New. Then in my receiver I look for all New records and changed the status to Imported. Then to change the status for records created manually I added this to my response_add method in admin class:

    def response_add(self, request, obj, post_url_continue=None):
        obj.status = 'Created'
        obj.save()
        return super(CustomUserAdmin, self).response_add(request, obj, post_url_continue)

@dco5
Copy link

dco5 commented Mar 17, 2020

i am having the same issue, I am using django-elasticsearch-dsl to index new created objects into elasticsearch, but when I use django-import-export the save post_save signal is triggering twice, so it indexes the imported row twice. Is there a way to disable signals if the import is a dry-run?

@thibaut-pro
Copy link

thibaut-pro commented Jul 31, 2020

I have been hit by this as well. I wouldn't expect post_save hooks to be called until I actually click "Confirm Import".

@pratikgajjar
Copy link

pratikgajjar commented Aug 20, 2020

I have described detailed issue in pr, why it happens : #1176
Use this fix, inherit below class

class BaseModelResource(resources.ModelResource):
    save_points = []

    def before_save_instance(self, instance, using_transactions, dry_run):
        """
        Override to add additional logic. Does nothing by default.
        Saving intermediate savepoints of txn
        """
        if using_transactions and dry_run:
            con = transaction.get_connection()
            self.save_points.extend(con.savepoint_ids)

    def after_import(self, dataset, result, using_transactions, dry_run, **kwargs):
        """
        Override to add additional logic. Does nothing by default.
        Manually removing commit hooks for intermediate savepoints of atomic transaction
        """
        if using_transactions and dry_run:
            con = transaction.get_connection()
            for sid in self.save_points:
                con.run_on_commit = [
                    (sids, func) for (sids, func) in con.run_on_commit if sid not in sids
                ]
        super().after_import(dataset, result, using_transactions, dry_run, **kwargs)

@andrewgy8
Copy link
Member

andrewgy8 commented Sep 7, 2020

I was recently hit by something similar to this issue and I solved it by doing something this like this https://stackoverflow.com/a/33192224/5779280 . Would adding a temporary value to the instance be a solution to this problem?

I admit its not an elegant solution, but I think it would work and should be quite simple to add to the package.

@pokken-magic
Copy link

I’ve been thinking on this one a bit and it looks like it is probably still an issue. Interestingly for people who are not using transactions in every day code but want to use them in import export they would be forced to change their code to support it.

it feels like a specific quirk of import export that we often roll back transactions so that makes me think our atypical use of transactions is the issue (if I understand right anyway).

It seems like this could affect all signals fired by a model not just post save? Wouldn’t any signals triggered by the model creation or modification process be triggered?

Maybe it’s really a django bug that signals should not fire until a transaction is committed if they would be generated?

@fordguo
Copy link

fordguo commented Jul 13, 2022

There is a solution with dry_run:
https://stackoverflow.com/a/71625152/254762

@TadasPik
Copy link

+1 on this. Disabling signals seems only one reasonable approach once on dry run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest OSS free-for-all
Projects
None yet
Development

No branches or pull requests

9 participants