python - pages - listar registros django
Duplicar instancias de modelo y sus objetos relacionados en Django/Algorithm para duplicar recursivamente un objeto (10)
Tengo modelos para Books
, Chapters
y Pages
. Todos están escritos por un User
:
from django.db import models
class Book(models.Model)
author = models.ForeignKey(''auth.User'')
class Chapter(models.Model)
author = models.ForeignKey(''auth.User'')
book = models.ForeignKey(Book)
class Page(models.Model)
author = models.ForeignKey(''auth.User'')
book = models.ForeignKey(Book)
chapter = models.ForeignKey(Chapter)
Lo que me gustaría hacer es duplicar un Book
existente y actualizarlo a otra persona. La cuestión es que también me gustaría duplicar todas las instancias de modelos relacionadas con el Book
, ¡todos sus Chapters
y Pages
también!
Las cosas se vuelven realmente complicadas cuando se mira una Page
, no solo las nuevas Pages
deben tener su campo de author
actualizado sino que también deberán señalar los nuevos objetos del Chapter
.
¿Admite Django una forma original de hacer esto? ¿Cómo sería un algoritmo genérico para duplicar un modelo?
Aclamaciones,
John
Actualizar:
¡Las clases dadas arriba son solo un ejemplo para ilustrar el problema que estoy teniendo!
Manera simple no genérica
Las soluciones propuestas no me funcionaron, así que fui simple, no inteligente. Esto solo es útil para casos simples.
Para un modelo con la siguiente estructura
Book
|__ CroppedFace
|__ Photo
|__ AwsReco
|__ AwsLabel
|__ AwsFace
|__ AwsEmotion
esto funciona
def duplicate_book(book: Book, new_user: MyUser):
# AwsEmotion, AwsFace, AwsLabel, AwsReco, Photo, CroppedFace, Book
old_cropped_faces = book.croppedface_set.all()
old_photos = book.photo_set.all()
book.pk = None
book.user = new_user
book.save()
for cf in old_cropped_faces:
cf.pk = None
cf.book = book
cf.save()
for photo in old_photos:
photo.pk = None
photo.book = book
photo.save()
if hasattr(photo, ''awsreco''):
reco = photo.awsreco
old_aws_labels = reco.awslabel_set.all()
old_aws_faces = reco.awsface_set.all()
reco.pk = None
reco.photo = photo
reco.save()
for label in old_aws_labels:
label.pk = None
label.reco = reco
label.save()
for face in old_aws_faces:
old_aws_emotions = face.awsemotion_set.all()
face.pk = None
face.reco = reco
face.save()
for emotion in old_aws_emotions:
emotion.pk = None
emotion.aws_face = face
emotion.save()
return book
Aquí hay una manera fácil de copiar su objeto.
Básicamente:
(1) establezca el id de su objeto original en Ninguno:
book_to_copy.id = Ninguna
(2) cambie el atributo ''autor'' y guarde el efecto:
book_to_copy.author = new_author
book_to_copy.save ()
(3) INSERT realizado en lugar de ACTUALIZAR
(No aborda el cambio del autor en la Página - Estoy de acuerdo con los comentarios sobre la reestructuración de los modelos)
Creo que también estarías más feliz con un modelo de datos más simple.
¿Es realmente cierto que una Página está en algún Capítulo pero es un libro diferente?
userMe = User( username="me" )
userYou= User( username="you" )
bookMyA = Book( userMe )
bookYourB = Book( userYou )
chapterA1 = Chapter( book= bookMyA, author=userYou ) # "me" owns the Book, "you" owns the chapter?
chapterB2 = Chapter( book= bookYourB, author=userMe ) # "you" owns the book, "me" owns the chapter?
page1 = Page( book= bookMyA, chapter= chapterB2, author=userMe ) # Book and Author aggree, chapter doesn''t?
Parece que tu modelo es demasiado complejo.
Creo que estarías más feliz con algo más simple. Solo estoy adivinando esto, ya que no conozco todo el problema.
class Book(models.Model)
name = models.CharField(...)
class Chapter(models.Model)
name = models.CharField(...)
book = models.ForeignKey(Book)
class Page(models.Model)
author = models.ForeignKey(''auth.User'')
chapter = models.ForeignKey(Chapter)
Cada página tiene una autoría distinta. Cada capítulo, entonces, tiene una colección de autores, al igual que el libro. Ahora puede duplicar Libro, Capítulo y Páginas, asignando las Páginas clonadas al nuevo Autor.
De hecho, es posible que desee tener una relación de varios a varios entre Página y Capítulo, lo que le permite tener múltiples copias de solo la Página, sin clonar el libro y el Capítulo.
Django tiene una forma integrada de duplicar un objeto a través del administrador, tal como se responde aquí: en la interfaz de administración de Django, ¿hay alguna forma de duplicar un elemento?
El uso del fragmento de CollectedObjects anterior ya no funciona, pero se puede hacer con la siguiente modificación:
from django.contrib.admin.util import NestedObjects
from django.db import DEFAULT_DB_ALIAS
y
collector = NestedObjects(using=DEFAULT_DB_ALIAS)
en lugar de CollectorObjects
En Django 1.5 esto funciona para mí:
thing.id = None
thing.pk = None
thing.save()
Esto ya no funciona en Django 1.3 ya que CollectedObjects fue eliminado. Ver el conjunto de cambios 14507
Publiqué mi solución en Django Snippets. Se basa en gran medida en el código django.db.models.query.CollectedObject
utilizado para eliminar objetos:
from django.db.models.query import CollectedObjects
from django.db.models.fields.related import ForeignKey
def duplicate(obj, value, field):
"""
Duplicate all related objects of `obj` setting
`field` to `value`. If one of the duplicate
objects has an FK to another duplicate object
update that as well. Return the duplicate copy
of `obj`.
"""
collected_objs = CollectedObjects()
obj._collect_sub_objects(collected_objs)
related_models = collected_objs.keys()
root_obj = None
# Traverse the related models in reverse deletion order.
for model in reversed(related_models):
# Find all FKs on `model` that point to a `related_model`.
fks = []
for f in model._meta.fields:
if isinstance(f, ForeignKey) and f.rel.to in related_models:
fks.append(f)
# Replace each `sub_obj` with a duplicate.
sub_obj = collected_objs[model]
for pk_val, obj in sub_obj.iteritems():
for fk in fks:
fk_value = getattr(obj, "%s_id" % fk.name)
# If this FK has been duplicated then point to the duplicate.
if fk_value in collected_objs[fk.rel.to]:
dupe_obj = collected_objs[fk.rel.to][fk_value]
setattr(obj, fk.name, dupe_obj)
# Duplicate the object and save it.
obj.id = None
setattr(obj, field, value)
obj.save()
if root_obj is None:
root_obj = obj
return root_obj
Si solo hay un par de copias en la base de datos que está compilando, he descubierto que puede usar el botón Atrás en la interfaz de administración, cambiar los campos necesarios y guardar la instancia nuevamente. Esto me ha funcionado en casos en los que, por ejemplo, necesito crear un cóctel de "gimlet" y un "vodka gimlet" donde la única diferencia es reemplazar el nombre y un ingrediente. Obviamente, esto requiere un poco de previsión de los datos y no es tan poderoso como anular la copia / copia profunda de django, pero puede ser el truco para algunos.
esta es una edición de http://www.djangosnippets.org/snippets/1282/
Ahora es compatible con el recopilador que reemplazó a CollectedObjects en 1.3.
Realmente no probé esto demasiado, pero lo probé con un objeto con aproximadamente 20,000 subobjetos, pero en solo tres capas de profundidad de clave externa. Úselo bajo su propio riesgo, por supuesto.
Para el tipo ambicioso que lee esta publicación, debe considerar la creación de subcategorías de Collector (o copiar toda la clase para eliminar esta dependencia en esta sección no publicada de la API django) a una clase llamada algo así como "DuplicateCollector" y escribir un método .duplicate que funcione de manera similar al método .delete. eso resolvería este problema de una manera real.
from django.db.models.deletion import Collector
from django.db.models.fields.related import ForeignKey
def duplicate(obj, value=None, field=None, duplicate_order=None):
"""
Duplicate all related objects of obj setting
field to value. If one of the duplicate
objects has an FK to another duplicate object
update that as well. Return the duplicate copy
of obj.
duplicate_order is a list of models which specify how
the duplicate objects are saved. For complex objects
this can matter. Check to save if objects are being
saved correctly and if not just pass in related objects
in the order that they should be saved.
"""
collector = Collector({})
collector.collect([obj])
collector.sort()
related_models = collector.data.keys()
data_snapshot = {}
for key in collector.data.keys():
data_snapshot.update({ key: dict(zip([item.pk for item in collector.data[key]], [item for item in collector.data[key]])) })
root_obj = None
# Sometimes it''s good enough just to save in reverse deletion order.
if duplicate_order is None:
duplicate_order = reversed(related_models)
for model in duplicate_order:
# Find all FKs on model that point to a related_model.
fks = []
for f in model._meta.fields:
if isinstance(f, ForeignKey) and f.rel.to in related_models:
fks.append(f)
# Replace each `sub_obj` with a duplicate.
if model not in collector.data:
continue
sub_objects = collector.data[model]
for obj in sub_objects:
for fk in fks:
fk_value = getattr(obj, "%s_id" % fk.name)
# If this FK has been duplicated then point to the duplicate.
fk_rel_to = data_snapshot[fk.rel.to]
if fk_value in fk_rel_to:
dupe_obj = fk_rel_to[fk_value]
setattr(obj, fk.name, dupe_obj)
# Duplicate the object and save it.
obj.id = None
if field is not None:
setattr(obj, field, value)
obj.save()
if root_obj is None:
root_obj = obj
return root_obj
EDITAR: eliminó una declaración de "impresión" de depuración.