Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

birger/1434 duplicate #1493

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 0 additions & 46 deletions apis_core/apis_metainfo/models.py
Original file line number Diff line number Diff line change
@@ -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.apis_metainfo 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):
"""
Expand All @@ -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):
Expand Down
4 changes: 0 additions & 4 deletions apis_core/apis_metainfo/signals.py

This file was deleted.

2 changes: 1 addition & 1 deletion apis_core/apis_relations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from django.db.models.signals import m2m_changed
from model_utils.managers import InheritanceManager

from apis_core.apis_metainfo import signals
from apis_core.apis_metainfo.models import RootObject
from apis_core.generic import signals
from apis_core.generic.abc import GenericModel
from apis_core.history.models import VersionMixin
from apis_core.utils import DateParser
Expand Down
3 changes: 1 addition & 2 deletions apis_core/apis_relations/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from django.dispatch import receiver

from apis_core.apis_metainfo.models import RootObject
from apis_core.apis_metainfo.signals import post_duplicate
from apis_core.apis_relations.models import TempTriple
from apis_core.generic.signals import post_merge_with
from apis_core.generic.signals import post_duplicate, post_merge_with

logger = logging.getLogger(__name__)

Expand Down
53 changes: 52 additions & 1 deletion apis_core/generic/abc.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions apis_core/generic/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

pre_merge_with = ModelSignal(use_caching=True)
post_merge_with = ModelSignal(use_caching=True)
pre_duplicate = ModelSignal(use_caching=True)
post_duplicate = ModelSignal(use_caching=True)
3 changes: 1 addition & 2 deletions apis_core/relations/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from django.contrib.contenttypes.models import ContentType
from django.dispatch import receiver

from apis_core.apis_metainfo.signals import post_duplicate
from apis_core.generic.signals import post_merge_with
from apis_core.generic.signals import post_duplicate, post_merge_with
from apis_core.relations.models import Relation

logger = logging.getLogger(__name__)
Expand Down
Loading