From 4b458099cf515e92c0b3692942a0b338fc52e455 Mon Sep 17 00:00:00 2001
From: Zainab Amir <zainab.amir@arbisoft.com>
Date: Mon, 3 Feb 2020 13:21:44 +0500
Subject: [PATCH] Add unique_together to CourseEntitlement (#22948)

Add unique_together on course_uuid and order_number to avoid
duplicate records

PROD-1064
---
 .../entitlements/api/v1/tests/test_views.py   | 53 +++++++++++++++++++
 .../0015_add_unique_together_constraint.py    | 29 ++++++++++
 common/djangoapps/entitlements/models.py      |  5 +-
 3 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 common/djangoapps/entitlements/migrations/0015_add_unique_together_constraint.py

diff --git a/common/djangoapps/entitlements/api/v1/tests/test_views.py b/common/djangoapps/entitlements/api/v1/tests/test_views.py
index 2006b9f520e..3428ce72ebf 100644
--- a/common/djangoapps/entitlements/api/v1/tests/test_views.py
+++ b/common/djangoapps/entitlements/api/v1/tests/test_views.py
@@ -141,6 +141,59 @@ class EntitlementViewSetTest(ModuleStoreTestCase):
         )
         assert results == CourseEntitlementSerializer(course_entitlement).data
 
+    def test_add_duplicate_entitlement(self):
+        """
+        Request with identical course_uuid and order_number should not create duplicate
+        entitlement
+        """
+        course_uuid = uuid.uuid4()
+        entitlement_data = self._get_data_set(self.user, str(course_uuid))
+
+        response = self.client.post(
+            self.entitlements_list_url,
+            data=json.dumps(entitlement_data),
+            content_type='application/json',
+        )
+        assert response.status_code == 201
+        response = self.client.post(
+            self.entitlements_list_url,
+            data=json.dumps(entitlement_data),
+            content_type='application/json',
+        )
+        assert response.status_code == 400
+        course_entitlement = CourseEntitlement.objects.filter(
+            course_uuid=course_uuid,
+            order_number=entitlement_data['order_number']
+        )
+        assert course_entitlement.count() == 1
+
+    def test_order_number_null(self):
+        """
+        Test that for same course_uuid order_number set to null is treated as unique
+        entitlement
+        """
+        course_uuid = uuid.uuid4()
+        entitlement_data = self._get_data_set(self.user, str(course_uuid))
+        entitlement_data['order_number'] = None
+
+        response = self.client.post(
+            self.entitlements_list_url,
+            data=json.dumps(entitlement_data),
+            content_type='application/json',
+        )
+        assert response.status_code == 201
+        response = self.client.post(
+            self.entitlements_list_url,
+            data=json.dumps(entitlement_data),
+            content_type='application/json',
+        )
+        assert response.status_code == 201
+        course_entitlement = CourseEntitlement.objects.filter(
+            course_uuid=course_uuid,
+            order_number=entitlement_data['order_number']
+        )
+        assert course_entitlement.count() == 2
+
     def test_default_no_policy_entry(self):
         """
         Verify that, when there are no entries in the course entitlement policy table,
diff --git a/common/djangoapps/entitlements/migrations/0015_add_unique_together_constraint.py b/common/djangoapps/entitlements/migrations/0015_add_unique_together_constraint.py
new file mode 100644
index 00000000000..cb6b1e54ea8
--- /dev/null
+++ b/common/djangoapps/entitlements/migrations/0015_add_unique_together_constraint.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.27 on 2020-01-29 10:33
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('entitlements', '0014_auto_20200115_2022'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='courseentitlement',
+            name='order_number',
+            field=models.CharField(default=None, max_length=128, null=True),
+        ),
+        migrations.AlterField(
+            model_name='historicalcourseentitlement',
+            name='order_number',
+            field=models.CharField(default=None, max_length=128, null=True),
+        ),
+        migrations.AlterUniqueTogether(
+            name='courseentitlement',
+            unique_together=set([('course_uuid', 'order_number')]),
+        ),
+    ]
diff --git a/common/djangoapps/entitlements/models.py b/common/djangoapps/entitlements/models.py
index f018d3e0ba9..08e93690382 100644
--- a/common/djangoapps/entitlements/models.py
+++ b/common/djangoapps/entitlements/models.py
@@ -171,12 +171,15 @@ class CourseEntitlement(TimeStampedModel):
         blank=True,
         on_delete=models.CASCADE,
     )
-    order_number = models.CharField(max_length=128, null=True, blank=True)
+    order_number = models.CharField(max_length=128, default=None, null=True)
     refund_locked = models.BooleanField(default=False)
     _policy = models.ForeignKey(CourseEntitlementPolicy, null=True, blank=True, on_delete=models.CASCADE)
 
     history = HistoricalRecords()
 
+    class Meta:
+        unique_together = ('course_uuid', 'order_number')
+
     @property
     def expired_at_datetime(self):
         """
-- 
GitLab