|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+# Generated by Django 3.2.9 on 2022-01-25 19:01
|
|
|
2
|
+
|
|
|
3
|
+from django.db import migrations
|
|
|
4
|
+from django.db.models import CharField, Count, Value
|
|
|
5
|
+from django.db.models.functions import Concat
|
|
|
6
|
+
|
|
|
7
|
+def remove_duplicate_attributes(apps, schema_editor):
|
|
|
8
|
+ """
|
|
|
9
|
+ Removes duplicate attributes that have the same code and product class.
|
|
|
10
|
+ """
|
|
|
11
|
+ ProductAttribute = apps.get_model('catalogue', 'ProductAttribute')
|
|
|
12
|
+ ProductClass = apps.get_model("catalogue", "ProductClass")
|
|
|
13
|
+
|
|
|
14
|
+ # Instead of iterating over all attributes, we concat the code and product class pk
|
|
|
15
|
+ # with a "|" so we can find duplicate attributes in one query.
|
|
|
16
|
+ duplicate_attributes = ProductAttribute.objects.annotate(
|
|
|
17
|
+ code_and_product_class=Concat('code', Value('|'), 'product_class__pk', output_field=CharField())
|
|
|
18
|
+ ).values('code_and_product_class').annotate(
|
|
|
19
|
+ same_code_count=Count('code_and_product_class')
|
|
|
20
|
+ ).filter(same_code_count__gt=1)
|
|
|
21
|
+
|
|
|
22
|
+ for attribute in duplicate_attributes:
|
|
|
23
|
+ attribute_code, product_class_pk = attribute["code_and_product_class"].split("|")
|
|
|
24
|
+ product_class = ProductClass.objects.get(pk=product_class_pk)
|
|
|
25
|
+ attributes = ProductAttribute.objects.filter(
|
|
|
26
|
+ code=attribute_code,
|
|
|
27
|
+ product_class=product_class
|
|
|
28
|
+ )
|
|
|
29
|
+ used_attributes = attributes.filter(productattributevalue__isnull=False)
|
|
|
30
|
+ used_attribute_count = used_attributes.distinct().count()
|
|
|
31
|
+
|
|
|
32
|
+ # In most cases, the used attributes count will be one or zero as
|
|
|
33
|
+ # the dashboard will always show one attribute. If the used attribute
|
|
|
34
|
+ # count is one, we exclude this from attributes and remove the others.
|
|
|
35
|
+ # If it's zero, we pick the last created and delete others.
|
|
|
36
|
+ if used_attribute_count == 1:
|
|
|
37
|
+ attributes.exclude(pk=used_attributes.first().pk).delete()
|
|
|
38
|
+ continue
|
|
|
39
|
+ elif used_attribute_count == 0:
|
|
|
40
|
+ attributes.exclude(pk=attributes.last().pk).delete()
|
|
|
41
|
+ continue
|
|
|
42
|
+
|
|
|
43
|
+ # If we found multiple attributes that have values linked to them,
|
|
|
44
|
+ # we must move them to one attribute and then delete the others.
|
|
|
45
|
+ # We can only do this if the value_types are all the same!
|
|
|
46
|
+ ASSERTION_MESSAGE = """Duplicate attribute found with code: %s but different types!
|
|
|
47
|
+ You could fix this by renaming the duplicate codes or by matching all types to one
|
|
|
48
|
+ type and update the attribute values accordingly for their new type. After that you can
|
|
|
49
|
+ re-run the migration.""" % attribute_code
|
|
|
50
|
+ assert used_attributes.values("type").distinct().count() == 1, ASSERTION_MESSAGE
|
|
|
51
|
+
|
|
|
52
|
+ # Choose one attribute that will be used to move to and others to be deleted.
|
|
|
53
|
+ to_be_used_attribute = used_attributes.first()
|
|
|
54
|
+ to_be_deleted_attributes = used_attributes.exclude(pk=to_be_used_attribute.pk)
|
|
|
55
|
+ for attribute in to_be_deleted_attributes:
|
|
|
56
|
+ attribute.productattributevalue_set.all().update(attribute=to_be_used_attribute)
|
|
|
57
|
+ attribute.delete()
|
|
|
58
|
+
|
|
|
59
|
+
|
|
|
60
|
+
|
|
|
61
|
+class Migration(migrations.Migration):
|
|
|
62
|
+
|
|
|
63
|
+ dependencies = [
|
|
|
64
|
+ ('catalogue', '0023_auto_20210824_1414'),
|
|
|
65
|
+ ]
|
|
|
66
|
+
|
|
|
67
|
+ operations = [
|
|
|
68
|
+ migrations.RunPython(remove_duplicate_attributes, migrations.RunPython.noop)
|
|
|
69
|
+ ]
|