angrybeanie_wagtail/env/lib/python3.12/site-packages/wagtail/models/copying.py

114 lines
3.8 KiB
Python
Raw Normal View History

2025-07-25 21:32:16 +10:00
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.models import ClusterableModel
def _extract_field_data(source, exclude_fields=None):
"""
Get dictionaries representing the model's field data.
This excludes many to many fields (which are handled by _copy_m2m_relations)'
"""
exclude_fields = exclude_fields or []
data_dict = {}
for field in source._meta.get_fields():
# Ignore explicitly excluded fields
if field.name in exclude_fields:
continue
# Ignore reverse relations
if field.auto_created:
continue
# Ignore reverse generic relations
if isinstance(field, GenericRelation):
continue
# Copy parental m2m relations
if field.many_to_many:
if isinstance(field, ParentalManyToManyField):
parental_field = getattr(source, field.name)
if hasattr(parental_field, "all"):
values = parental_field.all()
if values:
data_dict[field.name] = values
continue
# Ignore parent links (page_ptr)
if isinstance(field, models.OneToOneField) and field.remote_field.parent_link:
continue
if isinstance(field, models.ForeignKey):
# Use attname to copy the ID instead of retrieving the instance
# Note: We first need to set the field to None to unset any object
# that's there already just setting _id on its own won't change the
# field until its saved.
data_dict[field.name] = None
data_dict[field.attname] = getattr(source, field.attname)
else:
data_dict[field.name] = getattr(source, field.name)
return data_dict
def _copy_m2m_relations(source, target, exclude_fields=None, update_attrs=None):
"""
Copies non-ParentalManyToMany m2m relations
"""
update_attrs = update_attrs or {}
exclude_fields = exclude_fields or []
for field in source._meta.get_fields():
# Copy m2m relations. Ignore explicitly excluded fields, reverse relations, and Parental m2m fields.
if (
field.many_to_many
and field.name not in exclude_fields
and not field.auto_created
and not isinstance(field, ParentalManyToManyField)
):
try:
# Do not copy m2m links with a through model that has a ParentalKey to the model being copied - these will be copied as child objects
through_model_parental_links = [
field
for field in field.through._meta.get_fields()
if isinstance(field, ParentalKey)
and issubclass(source.__class__, field.related_model)
]
if through_model_parental_links:
continue
except AttributeError:
pass
if field.name in update_attrs:
value = update_attrs[field.name]
else:
value = getattr(source, field.name).all()
getattr(target, field.name).set(value)
def _copy(source, exclude_fields=None, update_attrs=None):
data_dict = _extract_field_data(source, exclude_fields=exclude_fields)
target = source.__class__(**data_dict)
if update_attrs:
for field, value in update_attrs.items():
if field not in data_dict:
continue
setattr(target, field, value)
if isinstance(source, ClusterableModel):
child_object_map = source.copy_all_child_relations(
target, exclude=exclude_fields
)
else:
child_object_map = {}
return target, child_object_map