land-of-apps / django-oscar__django-oscar

Domain-driven e-commerce for Django
http://oscarcommerce.com
BSD 3-Clause "New" or "Revised" License
0 stars 0 forks source link

Error if create voucher name matching to voucher name deleted #2

Open kgilpin opened 3 months ago

kgilpin commented 3 months ago

Error if create voucher name matching to voucher name deleted

IntegrityError at /en-gb/dashboard/vouchers/create/
UNIQUE constraint failed: offer_conditionaloffer.name
Request Method: POST
Request URL: https://latest.oscarcommerce.com/en-gb/dashboard/vouchers/create/
Django Version: 2.2.9
Exception Type: IntegrityError
Exception Value: UNIQUE constraint failed: offer_conditionaloffer. name
Exception Location: /ust/ocal/lib/python3.7/sipackages/django/db/backends/salite3/base.py in execute, line 383
Python Executable: /us/local/bin/uwsgi
Python Version: 3.7.6
Python Path: ['/ app/sandbox/*,
• /usr/local/lib/python37.zip',
'/usr/local/lib/python3.7',
• /usr/local/lib/python3.7/lib-dynload',
• /usr/local/lib/python3.7/site-packages',
'/app/src' ]

Server time: Thu, 9 Jan 2020 02:36:46 +0000

Got error 500 when create voucher

Steps to Reproduce

Create new voucher Delete that voucher Create voucher again with same voucher name deleted

Suggestions

Oscar's models do not prevent the same offer being used in multiple places - so indiscriminately deleting all offers could affect other vouchers or remove an offer that is used for another purpose.

We should check that :

I also think this should be done in a post_delete signal so that there is no risk of integrity errors if the delete fails for another reason.

kgilpin commented 3 months ago

Title

Resolve UNIQUE constraint violation when creating a voucher with a previously deleted voucher's name

Problem

There is an IntegrityError when trying to create a voucher with the same name as a previously deleted voucher. This error occurs because the offer_conditionaloffer table has a unique constraint on the name field. When a voucher is deleted, the corresponding offer is not deleted, causing subsequent attempts to create a voucher with the same name to fail.

Analysis

The error arises due to the unique constraint on the name field of the offer_conditionaloffer table. When a voucher is deleted, the associated offer should also be deleted. However, this does not currently happen, leading to the unique constraint being violated on subsequent voucher creations with the same name. To resolve this issue, we need to ensure that the offer associated with the voucher is also deleted when the voucher is deleted.

We should implement the following:

  1. Ensure that only offers associated with the voucher are of the voucher type.
  2. Verify that the offer name corresponds to the voucher name.
  3. Use Django's post_delete signal to delete the associated offer when a voucher is deleted.

Proposed Changes

  1. src/oscar/apps/voucher/migrations/0009_make_voucher_names_unique.py: Modify this migration script to ensure that all existing voucher names are unique.

  2. src/oscar/apps/dashboard/vouchers/views.py:

    • Update the VoucherDeleteView to ensure that associated offers are deleted when the voucher is deleted.
    • Use the post_delete signal to automate the deletion of related offers.
  3. src/oscar/apps/voucher/models.py:

    • Add a post_delete signal handler to delete associated offers when a voucher is deleted.

Detailed Steps:

  1. Migration Script:

    • Modify the script to handle existing vouchers with non-unique names.
  2. VoucherDeleteView Changes:

    from django.db.models.signals import post_delete
    from django.dispatch import receiver
    from oscar.apps.voucher.models import Voucher
    
    @receiver(post_delete, sender=Voucher)
    def delete_associated_offers(sender, instance, **kwargs):
        offers = ConditionalOffer.objects.filter(name=instance.name, offer_type=ConditionalOffer.VOUCHER)
        for offer in offers:
            if offer.vouchers.count() == 1 and offer.vouchers.first() == instance:
                offer.delete()
  3. Signal Handler:

    from django.db.models.signals import post_delete
    from django.dispatch import receiver
    from oscar.apps.offer.models import ConditionalOffer
    
    @receiver(post_delete, sender=Voucher)
    def delete_associated_offers(sender, instance, **kwargs):
        offers = ConditionalOffer.objects.filter(name=instance.name, offer_type=ConditionalOffer.VOUCHER)
        for offer in offers:
            if offer.vouchers.count() == 1 and offer.vouchers.first() == instance:
                offer.delete()
  4. Ensure the new migration script is applied to the database:

    • Ensure that the modified migration script runs to update existing voucher names.
    • Run the command:
      python manage.py migrate

By implementing these changes, deleting a voucher will also delete the associated offer, resolving the issue where recreating vouchers with previously used names caused an IntegrityError.