Repository: fidals/shopelectro

Issue:
Fix product page
**Description**
Get 500 error from product page

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://www.shopelectro.ru/catalog/products/276/

**Screenshots**

<img width="705" alt="default" src="https://user-images.githubusercontent.com/13870342/45000079-94d45c80-afca-11e8-8fc9-bd1800332691.png">

**Additional context**
traceback:
```python
se-python                | Internal Server Error: /catalog/products/276/
se-python                | Traceback (most recent call last):
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 39, in inner
se-python                |     response = get_response(request)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
se-python                |     response = self.process_exception_by_middleware(e, request)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
se-python                |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
se-python                |     return self.dispatch(request, *args, **kwargs)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/utils/decorators.py", line 67, in _wrapper
se-python                |     return bound_func(*args, **kwargs)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
se-python                |     response = view_func(request, *args, **kwargs)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/utils/decorators.py", line 63, in bound_func
se-python                |     return func.__get__(self, type(self))(*args2, **kwargs2)
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/views/generic/base.py", line 88, in dispatch
se-python                |     return handler(request, *args, **kwargs)
se-python                |   File "/usr/app/src/shopelectro/views/catalog.py", line 112, in get
se-python                |     self.object = self.get_object()
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/views/generic/detail.py", line 53, in get_object
se-python                |     obj = queryset.get()
se-python                |   File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py", line 389, in get
se-python                |     (self.model._meta.object_name, num)
se-python                | shopelectro.models.MultipleObjectsReturned: get() returned more than one Product -- it returned 6!
```



Discussion:
I did not find any problems with the code, so it seems this issue only about corrupted data. I am going to create a migration for duplicated products.
@artemiy312 , you can check if data is duplicated with `python shell` on prod serv.
If it's true, prod serv contains such data, we should add some checks to price generation
@duker33 I already checked it and data is duplicated. I can not reproduce duplication of products as i said, and I have a guess, that we forgot to create data migration for an old resolved issue.

Resolving patch:
diff --git a/shopelectro/management/commands/_update_catalog/update_products.py b/shopelectro/management/commands/_update_catalog/update_products.py
--- a/shopelectro/management/commands/_update_catalog/update_products.py
+++ b/shopelectro/management/commands/_update_catalog/update_products.py
@@ -253,11 +253,15 @@ def save(product, field, value):
             for page_field, page_value in value.items():
                 if not getattr(product.page, page_field, ''):
                     setattr(product.page, page_field, page_value)
+        elif field == 'tags':
+            product.tags = merge(list(product.tags.all()), value)
         else:
             setattr(product, field, value)
 
     def merge(left: List, right: List) -> List:
         """Merge two arrays with order preserving."""
+        # Dirty patch for preserving tags, appended from admin.
+        # Still waiting 1C throwing out.
         return left + [e for e in right if e not in left]
 
     products = Product.objects.filter(uuid__in=data)
@@ -265,14 +269,9 @@ def merge(left: List, right: List) -> List:
     for product in products:
         product_data = data[str(product.uuid)]
         for field, value in product_data.items():
-            if field != 'tags':
-                save(product, field, value)
-            else:
-                # Dirty patch for preserving tags, appended from admin.
-                # Still waiting 1C throwing out.
-                product.tags = merge(list(product.tags.all()), value)
-
+            save(product, field, value)
         product.save()
+
     logger.info('{} products were updated.'.format(products.count()))
     return products
 
diff --git a/shopelectro/migrations/0026_remove_product_duplications.py b/shopelectro/migrations/0026_remove_product_duplications.py
new file mode 100644
--- /dev/null
+++ b/shopelectro/migrations/0026_remove_product_duplications.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2018-08-24 08:44
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+def migrate_forward(apps, schema_editor):
+    Product = apps.get_model('shopelectro', 'Product')
+
+    uuids_to_delete = [
+        result['uuid'] for result in
+        Product.objects.values('uuid').annotate(models.Count('uuid'))
+        .order_by().filter(uuid__count__gt=1)
+    ]
+
+    preserved = set()
+
+    for product in Product.objects.filter(uuid__in=uuids_to_delete):
+        if product.uuid in preserved:
+            product.delete()
+        else:
+            preserved.add(product.uuid)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('shopelectro', '0025_tag_unique_constraint'),
+    ]
+
+    operations = [
+        migrations.RunPython(migrate_forward),
+    ]


Test patch: