From 163120b98a9ab53c80732b3d599368db853ada38 Mon Sep 17 00:00:00 2001 From: Birger Schacht Date: Wed, 11 Dec 2024 09:43:59 +0100 Subject: [PATCH] refactor(generic,apis_metainfo): move duplicate logic to GenericModel --- apis_core/apis_metainfo/models.py | 46 --------------------------- apis_core/generic/abc.py | 53 ++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/apis_core/apis_metainfo/models.py b/apis_core/apis_metainfo/models.py index 503fade81..884a7e6f4 100644 --- a/apis_core/apis_metainfo/models.py +++ b/apis_core/apis_metainfo/models.py @@ -1,22 +1,16 @@ -import logging from urllib.parse import urlsplit from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured, ValidationError from django.db import models -from django.db.models.fields.related import ForeignKey, ManyToManyField from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor -from django.forms import model_to_dict from model_utils.managers import InheritanceManager -from apis_core.generic import signals from apis_core.generic.abc import GenericModel from apis_core.utils import rdf from apis_core.utils import settings as apis_settings from apis_core.utils.normalize import clean_uri -logger = logging.getLogger(__name__) - class RootObject(GenericModel, models.Model): """ @@ -41,46 +35,6 @@ def save(self, *args, **kwargs): self.self_contenttype = ContentType.objects.get_for_model(self) super().save(*args, **kwargs) - def duplicate(self): - origin = self.__class__ - signals.pre_duplicate.send(sender=origin, instance=self) - # usually, copying instances would work like - # https://docs.djangoproject.com/en/4.2/topics/db/queries/#copying-model-instances - # but we are working with abstract classes, - # so we have to do it by hand using model_to_dict:( - objdict = model_to_dict(self) - - # remove unique fields from dict representation - unique_fields = [field for field in self._meta.fields if field.unique] - for field in unique_fields: - logger.info(f"Duplicating {self}: ignoring unique field {field.name}") - objdict.pop(field.name, None) - - # remove related fields from dict representation - related_fields = [ - field for field in self._meta.get_fields() if field.is_relation - ] - for field in related_fields: - objdict.pop(field.name, None) - - newobj = type(self).objects.create(**objdict) - - for field in related_fields: - # we are not using `isinstance` because we want to - # differentiate between different levels of inheritance - if type(field) is ForeignKey: - setattr(newobj, field.name, getattr(self, field.name)) - if type(field) is ManyToManyField: - objfield = getattr(newobj, field.name) - values = getattr(self, field.name).all() - objfield.set(values) - - newobj.save() - signals.post_duplicate.send(sender=origin, instance=self, duplicate=newobj) - return newobj - - duplicate.alters_data = True - class InheritanceForwardManyToOneDescriptor(ForwardManyToOneDescriptor): def get_queryset(self, **hints): diff --git a/apis_core/generic/abc.py b/apis_core/generic/abc.py index 1d5c9ef8c..4cf185c09 100644 --- a/apis_core/generic/abc.py +++ b/apis_core/generic/abc.py @@ -1,10 +1,21 @@ +import logging + from django.contrib.contenttypes.models import ContentType from django.db.models import BooleanField, CharField, TextField +from django.db.models.fields.related import ForeignKey, ManyToManyField from django.db.models.query import QuerySet +from django.forms import model_to_dict from django.urls import reverse from apis_core.generic.helpers import permission_fullname -from apis_core.generic.signals import post_merge_with, pre_merge_with +from apis_core.generic.signals import ( + post_duplicate, + post_merge_with, + pre_duplicate, + pre_merge_with, +) + +logger = logging.getLogger(__name__) class GenericModel: @@ -163,3 +174,43 @@ def merge_with(self, entities): for ent in entities: ent.delete() + + def duplicate(self): + origin = self.__class__ + pre_duplicate.send(sender=origin, instance=self) + # usually, copying instances would work like + # https://docs.djangoproject.com/en/4.2/topics/db/queries/#copying-model-instances + # but we are working with abstract classes, + # so we have to do it by hand using model_to_dict:( + objdict = model_to_dict(self) + + # remove unique fields from dict representation + unique_fields = [field for field in self._meta.fields if field.unique] + for field in unique_fields: + logger.info(f"Duplicating {self}: ignoring unique field {field.name}") + objdict.pop(field.name, None) + + # remove related fields from dict representation + related_fields = [ + field for field in self._meta.get_fields() if field.is_relation + ] + for field in related_fields: + objdict.pop(field.name, None) + + newobj = type(self).objects.create(**objdict) + + for field in related_fields: + # we are not using `isinstance` because we want to + # differentiate between different levels of inheritance + if type(field) is ForeignKey: + setattr(newobj, field.name, getattr(self, field.name)) + if type(field) is ManyToManyField: + objfield = getattr(newobj, field.name) + values = getattr(self, field.name).all() + objfield.set(values) + + newobj.save() + post_duplicate.send(sender=origin, instance=self, duplicate=newobj) + return newobj + + duplicate.alters_data = True