Skip to content

Commit ced93b0

Browse files
authored
Fix problems introduced by duplicates (#137)
* Throw an exception when duplicates are found * When dupes are found, append a "-dedupe" to them. * Change the way product refs are being generated. Add a salt of randomness to avoid conflicts.
1 parent 2b0459e commit ced93b0

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

copanier/models.py

+42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inspect
22
import threading
33
import uuid
4+
from collections import Counter
45
from datetime import datetime, timedelta
56
from dataclasses import dataclass, field, asdict
67
from pathlib import Path
@@ -425,11 +426,52 @@ def load(cls, id):
425426
path = cls.get_root() / f"{id}.yml"
426427
if not path.exists():
427428
raise DoesNotExist
429+
430+
def _dedupe_products(raw_data):
431+
"""On some rare occasions, different products get
432+
the same identifier (ref).
433+
434+
This function finds them and appends "-dedupe" to it.
435+
This is not ideal but fixes the problem before it causes more
436+
trouble (such as https://github.com/spiral-project/copanier/issues/136)
437+
438+
This function returns True if dupes have been found.
439+
"""
440+
if ('products' not in raw_data) or len(raw_data['products']) < 1:
441+
return False
442+
443+
products = raw_data['products']
444+
445+
counter = Counter([p['ref'] for p in products])
446+
most_common = counter.most_common(1)[0]
447+
number_of_dupes = most_common[1]
448+
449+
if number_of_dupes < 2:
450+
return False
451+
452+
dupe_id = most_common[0]
453+
# Reconstruct the products list but change the duplicated ID.
454+
counter = 0
455+
new_products = []
456+
for product in products:
457+
ref = product['ref']
458+
if ref == dupe_id:
459+
counter = counter + 1
460+
if counter == number_of_dupes: # Only change the last occurence.
461+
product['ref'] = f'{ref}-dedupe'
462+
new_products.append(product)
463+
raw_data['products'] = new_products
464+
return True
465+
428466
data = yaml.safe_load(path.read_text())
467+
dupe_found = _dedupe_products(data)
429468
# Tolerate extra fields (but we'll lose them if instance is persisted)
430469
data = {k: v for k, v in data.items() if k in cls.__dataclass_fields__}
431470
delivery = cls(**data)
432471
delivery.id = id
472+
473+
if dupe_found:
474+
delivery.persist()
433475
return delivery
434476

435477
@classmethod

copanier/views/products.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from datetime import datetime
22

3+
import random
4+
import string
5+
36
from slugify import slugify
47
from .core import app
58
from ..models import Delivery, Product, Producer
@@ -153,7 +156,8 @@ async def create_product(request, response, delivery_id, producer_id):
153156
product.producer = producer_id
154157
form = request.form
155158
product.update_from_form(form)
156-
product.ref = slugify(f"{producer_id}-{product.name}-{product.unit}")
159+
random_string = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
160+
product.ref = slugify(f"{producer_id}-{product.name}-{product.unit}-{random_string}")
157161

158162
delivery.products.append(product)
159163
delivery.persist()

0 commit comments

Comments
 (0)