2021-01-01 00:03:54 +00:00
|
|
|
''' PROCEED WITH CAUTION: uses deduplication fields to permanently
|
|
|
|
merge book data objects '''
|
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from django.db.models import Count
|
|
|
|
from bookwyrm import models
|
|
|
|
|
|
|
|
|
2021-01-01 00:30:04 +00:00
|
|
|
def update_related(canonical, obj):
|
|
|
|
''' update all the models with fk to the object being removed '''
|
|
|
|
# move related models to canonical
|
|
|
|
related_models = [
|
|
|
|
(r.remote_field.name, r.related_model) for r in \
|
|
|
|
canonical._meta.related_objects]
|
|
|
|
for (related_field, related_model) in related_models:
|
|
|
|
related_objs = related_model.objects.filter(
|
|
|
|
**{related_field: obj})
|
|
|
|
for related_obj in related_objs:
|
|
|
|
print(
|
|
|
|
'replacing in',
|
|
|
|
related_model.__name__,
|
|
|
|
related_field,
|
|
|
|
related_obj.id
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
setattr(related_obj, related_field, canonical)
|
|
|
|
related_obj.save()
|
|
|
|
except TypeError:
|
|
|
|
getattr(related_obj, related_field).add(canonical)
|
|
|
|
getattr(related_obj, related_field).remove(obj)
|
|
|
|
|
|
|
|
|
|
|
|
def copy_data(canonical, obj):
|
|
|
|
''' try to get the most data possible '''
|
|
|
|
for data_field in obj._meta.get_fields():
|
|
|
|
if not hasattr(data_field, 'activitypub_field'):
|
|
|
|
continue
|
|
|
|
data_value = getattr(obj, data_field.name)
|
|
|
|
if not data_value:
|
|
|
|
continue
|
|
|
|
if not getattr(canonical, data_field.name):
|
|
|
|
print('setting data field', data_field.name, data_value)
|
|
|
|
setattr(canonical, data_field.name, data_value)
|
|
|
|
canonical.save()
|
|
|
|
|
|
|
|
|
2021-01-01 00:03:54 +00:00
|
|
|
def dedupe_model(model):
|
|
|
|
''' combine duplicate editions and update related models '''
|
|
|
|
fields = model._meta.get_fields()
|
|
|
|
dedupe_fields = [f for f in fields if \
|
|
|
|
hasattr(f, 'deduplication_field') and f.deduplication_field]
|
|
|
|
for field in dedupe_fields:
|
|
|
|
dupes = model.objects.values(field.name).annotate(
|
|
|
|
Count(field.name)
|
|
|
|
).filter(**{'%s__count__gt' % field.name: 1})
|
|
|
|
|
|
|
|
for dupe in dupes:
|
|
|
|
value = dupe[field.name]
|
|
|
|
if not value or value == '':
|
|
|
|
continue
|
|
|
|
print('----------')
|
|
|
|
print(dupe)
|
|
|
|
objs = model.objects.filter(
|
|
|
|
**{field.name: value}
|
|
|
|
).order_by('id')
|
|
|
|
canonical = objs.first()
|
2021-01-01 00:30:04 +00:00
|
|
|
print('keeping', canonical.remote_id)
|
2021-01-01 00:03:54 +00:00
|
|
|
for obj in objs[1:]:
|
2021-01-01 00:30:04 +00:00
|
|
|
print(obj.remote_id)
|
|
|
|
copy_data(canonical, obj)
|
|
|
|
update_related(canonical, obj)
|
|
|
|
# remove the outdated entry
|
2021-01-01 00:03:54 +00:00
|
|
|
obj.delete()
|
|
|
|
|
|
|
|
|
|
|
|
class Command(BaseCommand):
|
2021-01-01 00:30:04 +00:00
|
|
|
''' dedplucate allllll the book data models '''
|
2021-01-01 00:03:54 +00:00
|
|
|
help = 'merges duplicate book data'
|
2021-01-01 00:30:04 +00:00
|
|
|
# pylint: disable=no-self-use,unused-argument
|
2021-01-01 00:03:54 +00:00
|
|
|
def handle(self, *args, **options):
|
|
|
|
''' run deudplications '''
|
|
|
|
dedupe_model(models.Edition)
|
|
|
|
dedupe_model(models.Work)
|
|
|
|
dedupe_model(models.Author)
|