2011-04-13 18 views
13

He buscado en el desbordamiento de pila una respuesta a esta pregunta (probablemente simple), pero la mayoría de las soluciones que veo parecen demasiado complicadas y difíciles de entender.Campo de modelo de Django para la clase base abstracta

Tengo un modelo "Publicar" que es una clase base abstracta. Modelos "Anuncio" y "Evento" heredan de la publicación.

Ahora mismo estoy manteniendo listas de eventos y anuncios relacionados en otros modelos. Por ejemplo, tengo los campos "removed_events" y "removed_announcements" en otro modelo.

Sin embargo, en mi proyecto, "removed_events" y "removed_announcements" se tratan exactamente de la misma manera. No es necesario desambiguar entre un "evento eliminado" y un "anuncio eliminado". En otras palabras, sería suficiente con mantener un registro de campo de "removed_posts".

No sé cómo (o quizás no) crear un campo "removed_posts", ya que Post es abstracto. Sin embargo, en este momento siento que me estoy repitiendo en el código (y tengo que hacer un montón de desorden, algunos controles para descubrir si la publicación que estoy viendo es un evento o un anuncio y agregarlo al sitio apropiado). campo eliminado).

¿Cuál es la mejor opción aquí? Podría hacer que las publicaciones no sean abstractas, pero los objetos Post nunca deberían crearse, y no creo que pueda aplicar esto en un objeto no abstracto.

Mi comprensión de las bases de datos es débil, pero tengo la impresión de que hacer una publicación no abstracta complicaría la base de datos debido a las uniones. ¿Esto es un gran problema?

Finalmente, hay otros campos en otros modelos en los que me gustaría condensar cosas que equivalen a una event_list y una announce_list en una post_list, pero esos campos deben ser desambiguados. Podría filtrar la post_list según el tipo de publicación, pero la llamada a filter() sería más lenta que poder acceder directamente al evento y a las listas de anuncios por separado, ¿no? Alguna sugerencia aqui?

Gracias a todos por leer esto.

Respuesta

2

Las relaciones genéricas y las claves externas son su amigo en su camino para tener éxito. Defina un modelo intermedio donde un lado es genérico, luego el otro lado obtendrá una lista relacionada de modelos polimórficos. Es un poco más complicado que un modelo estándar de unión m2m, en el que el lado genérico tiene dos columnas, una para ContentType (en realidad, un FK) y la otra para el PK de la instancia del modelo vinculado real. También puede restringir los modelos a vincular con el uso de parámetros FK estándar. Te acostumbrarás rápidamente.

(ahora que me sale un teclado real para escribir, aquí está el ejemplo :)

class Post(models.Model): 
    class Meta: abstract = True 
    CONCRETE_CLASSES = ('announcement', 'event',) 
    removed_from = generic.GenericRelation('OwnerRemovedPost', 
     content_type_field='content_type', 
     object_id_field='post_id', 
    ) 

class Announcement(Post): pass 

class Event(Post): pass 

class Owner(models.Model): 

    # non-polymorphic m2m 
    added_events = models.ManyToManyField(Event, null=True) 

    # polymorphic m2m-like property 
    def removed_posts(self): 
     # can't use ManyToManyField with through. 
     # can't return a QuerySet b/c it would be a union. 
     return [i.post for i in self.removed_post_items.all()] 

    def removed_events(self): 
     # using Post's GenericRelation 
     return Event.objects.filter(removed_from__owner=self) 


class OwnerRemovedPost(models.Model): 
    content_type = models.ForeignKey(ContentType, 
     limit_choices_to={'name__in': Post.CONCRETE_CLASSES}, 
    ) 
    post_id = models.PositiveIntegerField() 
    post = generic.GenericForeignKey('content_type', 'post_id') 
    owner = models.ForeignKey(Owner, related_name='removed_post_items') 

    class Meta: 
     unique_together = (('content_type', 'post_id'),) # to fake FK constraint 

No se puede filtrar en la colección relacionado como un clásico de muchos a muchos, pero con los métodos adecuados en Owner, y usando inteligentemente los gerentes de las clases concretas, usted llega a donde quiera.

+0

Con una solución como esa, también podría usar GenericForeignKey para el anuncio/evento en el modelo que necesita la clave externa en lugar de usar el intermediario. –

+0

Dependiendo de si el modelado polimórfico original tenía un FK en lugar de un m2m, es cierto. De todos modos, es difícil eliminar claves externas cuando ya no las necesita, mucho más que tratar una m2m como un FK más un inverso uno a uno.Si el modelo debe ser lo suficientemente genérico, es parche m2m o mono, como en el campo explícito.contribute_to_class() ... – rewritten

11

Hay dos tipos de subclases de modelo en Django: clases base abstractas; y herencia de múltiples tablas.

Las clases base abstractas no se usan nunca por sí mismas, y no tienen una tabla de base de datos ni ninguna forma de identificación. Son simplemente una forma de acortar código, agrupando conjuntos de campos comunes en el código, no en la base de datos.

Por ejemplo:

class Address(models.Model): 
    street = ... 
    city = ... 

    class Meta: 
     abstract = True 


class Employee(Address): 
    name = ... 

class Employer(Address): 
    employees = ... 
    company_name = ... 

Este es un ejemplo artificial, pero como se puede ver, una Employee no es una Address, y tampoco es una Employer. Ambos contienen campos relacionados con una dirección. Solo hay dos tablas en este ejemplo; Employee y Employer - y ambos contienen todos los campos de Dirección. La dirección de un empleador no se puede comparar con la dirección de un empleado en el nivel de la base de datos; una dirección no tiene una clave propia.

Ahora, con la herencia de varias tablas, (eliminar el resumen = Verdadero de la dirección), la dirección tiene tiene una tabla en sí misma. Esto dará como resultado 3 tablas distintas; Address, Employer y Employee. Tanto el Empleador como el Empleado tendrán una clave externa única (OneToOneField) de vuelta a Dirección.

Ahora puede consultar una dirección sin preocuparse por el tipo de dirección que es.

for address in Address.objects.all(): 
    try: 
     print address.employer 
    except Employer.DoesNotExist: # must have been an employee 
     print address.employee 

Cada dirección tendrá su propia clave principal, lo que significa que se puede guardar en una cuarta tabla por sí solo:

class FakeAddresses(models.Model): 
    address = models.ForeignKey(Address) 
    note = ... 

multi-mesa de herencia es lo que está buscando, si necesita trabajar con objetos del tipo Post sin preocuparse por qué tipo de publicación es. Habrá una sobrecarga de un join si se accede a cualquiera de los campos de Post de la subclase; pero la sobrecarga será mínima. Es una unión de índice única, que debe ser increíblemente rápida.

Solo asegúrese de que, si necesita acceder al Post, use select_related en el conjunto de preguntas.

Events.objects.select_related(depth=1) 

Eso evitará consultas adicionales para recuperar los datos principales, pero dará lugar a la unión que se produzca. Entonces solo use seleccionar relacionado si necesita la publicación.

Dos notas finales; si una publicación puede ser tanto un anuncio como un evento, entonces debe hacer lo tradicional y vincularlo a la publicación a través de una clave externa. Ninguna subclasificación funcionará en este caso.

Lo último es que si las uniones son críticas para el rendimiento entre el padre y los hijos, debe usar la herencia abstracta; y usa relaciones genéricas para referirse a las publicaciones abstractas de una tabla que es mucho menos crítica para el rendimiento.

Relaciones Genérico almacenar esencialmente de datos de esta manera:

class GenericRelation(models.Model): 
    model = ... 
    model_key = ... 


DeletedPosts(models.Model): 
    post = models.ForeignKey(GenericRelation) 

Eso va a ser mucho más complicado para unirse en SQL (Django le ayuda con eso), sino que también será menos eficiente que un OneToOne sencilla unirse . Solo debe avanzar por esta ruta si OneToOne se une al rendimiento de su aplicación, lo que probablemente sea poco probable.

+0

Gracias por esto: su primer escenario, una referencia ForeignKey a la base de un árbol de herencia de tablas múltiples , es exactamente lo que esperaba usar Dio un error: "Error: uno o más modelos no validaron: ordering.orderinvoicelog: 'partner' tiene una relación con el modelo Partner, que no se ha instalado o es abstracto." que arreglé cambiando la declaración de relación de clave foránea para hacer referencia directamente a la clase base, en lugar de su nombre en una cadena. es decir, use ForeignKey (MyClass) en lugar de ForeignKey ('MyClass'). Este último funciona en otras circunstancias, pero no aquí. –

+0

¿Qué sucede si tengo 4 de esas subclases? ¿Cómo se verá el bloque try catch? – iankit

+0

@iankit busque algo que lo ayude, como https://github.com/carljm/django-model-utils –

Cuestiones relacionadas