2012-02-21 26 views
5

En mi proyecto Django, todas las entidades eliminadas por el usuario deben eliminarse de manera instantánea estableciendo el datetime actual en la propiedad deleted_at. Mi modelo se ve así: Trip < -> TripDestination < -> Destination (muchos a muchos). En otras palabras, un Viaje puede tener múltiples destinos.Cómo eliminar fácilmente la relación muchos a muchos con Django

Cuando elimino un Viaje, el Administrador de eliminación de errores borra todo el viaje eliminado. Sin embargo, si solicito todos los destinos de un viaje (usando get_object_or_404 (Trip, pk = id)), también obtengo los eliminados (es decir, modelos TripDestination con deleted_at == null O deleted_at! = Null). Realmente no entiendo por qué dado que todos mis modelos heredan de LifeTimeTracking y están usando SoftDeleteManager.

¿Puede alguien ayudarme a entender por qué SoftDeleteManager no funciona para la relación n: m?

class SoftDeleteManager(models.Manager): 
    def get_query_set(self): 
     query_set = super(SoftDeleteManager, self).get_query_set() 
     return query_set.filter(deleted_at__isnull = True) 

class LifeTimeTrackingModel(models.Model): 
    created_at = models.DateTimeField(auto_now_add = True) 
    updated_at = models.DateTimeField(auto_now = True) 
    deleted_at = models.DateTimeField(null = True) 

    objects = SoftDeleteManager() 
    all_objects = models.Manager() 

    class Meta: 
     abstract = True 

class Destination(LifeTimeTrackingModel): 
    city_name = models.CharField(max_length = 45) 

class Trip(LifeTimeTrackingModel): 
    name = models.CharField(max_length = 250) 
    destinations = models.ManyToManyField(Destination, through = 'TripDestination') 

class TripDestination(LifeTimeTrackingModel): 
    trip = models.ForeignKey(Trip) 
    destination = models.ForeignKey(Destination) 

Resolución me presentó el error 17746 en Django Bug DB. Gracias a Caspar por su ayuda en esto.

Respuesta

2

Parece que este comportamiento proviene de la ManyToManyField la elección de utilizar su propio gestor, que la Related objects reference menciones, porque cuando intento hacer algunas de mis propias instancias & tratar suave borrarlos usando el código del modelo (a través de la gestión .py shell) todo funciona según lo previsto.

Desafortunadamente, no menciona cómo puede anular el administrador de modelos. Pasé unos 15 minutos buscando en la fuente ManyToManyField pero no he rastreado dónde crea su administrador (mirando en django/db/models/fields/related.py).

Para obtener el comportamiento que está después, debe especificar use_for_related_fields = True en su clase SoftDeleteManager como se especifica en la documentación sobre controlling automatic managers:

class SoftDeleteManager(models.Manager): 
    use_for_related_fields = True 

    def get_query_set(self): 
     query_set = super(SoftDeleteManager, self).get_query_set() 
     return query_set.filter(deleted_at__isnull = True) 

Esto funciona como se esperaba: Soy capaz de definir una Trip con 2 Destination s, cada uno a través de un TripDestination, y si me puse un Destination 's valor deleted_at-datetime.datetime.now() entonces que Destination ya no aparece en la lista dada por mytrip.destinations.all(), que es lo que está después de lo que puedo contar.

Sin embargo, los documentos también dicen específicamente do not filter the query set by overriding get_query_set() on a manager used for related fields, así que si llegas a tener problemas más adelante, tener esto en cuenta como posible causa.

+0

@Martin He actualizado mi respuesta, mira si funciona para ti :) – Caspar

+0

Hola Caspar! Gracias por ayudarme. Pero lo que estoy buscando es no ver los destinos si se ha eliminado la Tripdesrimation, no Destination. – Martin

+0

Ah, ya veo lo que quieres decir. Lo probé y no funciona con mi solución, lo que creo que indica un error. Una [búsqueda rápida] (https://code.djangoproject.com/search?q=use_for_related_fields) a través de los errores db no enciende nada (aunque hay un error que es [casi lo contrario] (https: // code.djangoproject.com/ticket/14891)), entonces podría ser hora de provocar un error. – Caspar

2

para filtrar por deleted_at campo de la Destinantion y Trip modelos de fijación de use_for_related_fields = True para SoftDeleteManager clase es suficiente. Según la respuesta de Caspar esto no devuelve Destinations borrado para trip_object.destinations.all().

Sin embargo, desde sus comentarios podemos ver que le gustaría filtrar Destinations que están vinculados a través de un objeto TripTripDestination con un campo de juego de deleted_at, alias suave eliminar en un través ejemplo.

Aclaremos la forma en que trabajan los gerentes. Los gerentes relacionados son los gerentes del modelo remoto, no de un modelo directo.

trip_object.destinantions.some_method() llamadas predeterminado Destination manager. destinantion_object.trip_set.some_method() llamadas predeterminado administrador Trip. TripDestination administrador no se llama en ningún momento.

Puede llamarlo con trip_object.destinantions.through.objects.some_method(), si realmente lo desea. Ahora, lo que haría es agregar un método de instancia Trip.get_destinations y un Destination.get_trips similar que filtra las conexiones eliminadas.

Si insiste en usar el gestor de hacer el filtrado se vuelve más complicado:

class DestinationManager(models.Manager): 
    use_for_related_fields = True 

    def get_query_set(self): 
     query_set = super(DestinationManager, self).get_query_set() 
     if hasattr(self, "through"): 
      through_objects = self.through.objects.filter(
       destination_id=query_set.filter(**self.core_filters).get().id, 
       trip_id=self._fk_val, 
       deleted_at__isnull=True) 
      query_set = query_set.filter(
       id__in=through_objects.values("destination_id")) 

     return query_set.filter(deleted_at__isnull = True) 

La misma tendría que ser hecho por TripManager ya que serían diferentes. Puede consultar el rendimiento y mirar django/db/models/fields/related.py para referencia.

La modificación del método get_queryset del administrador predeterminado puede obstaculizar la capacidad de realizar copias de seguridad de la base de datos y la documentación lo desalienta. Escribir un método Trip.get_destinations es la alternativa.

Cuestiones relacionadas