2009-09-14 13 views
12

En mi sitio django tengo dos aplicaciones, blog y enlaces. blog tiene un blogpost modelo, y enlaces tiene un enlace modelo. Debe haber una relación de uno a muchos entre estas dos cosas. Hay muchos enlaces por blogpost, pero cada enlace tiene una y solo una publicación en el blog. La respuesta simple es colocar una ForeignKey en blogpost en el modelo de enlace.Cómo modelar una clave externa en una aplicación Django reutilizable?

Eso está muy bien, sin embargo, hay un problema. Quiero que la aplicación de enlaces sea reutilizable. No quiero que dependa de la aplicación del blog. Quiero poder utilizarlo de nuevo en otros sitios y tal vez asociar enlaces con otras aplicaciones y modelos que no sean blogpost.

Una clave externa genérica parece que podría ser la respuesta, pero no realmente. No deseo que los enlaces se puedan asociar con ningún modelo en mi sitio. Solo el que especifico explícitamente. Y sé por experiencia previa que puede haber problemas al usar claves externas genéricas en términos de uso de la base de datos porque no se puede hacer una select_related sobre una clave externa genérica de la forma en que se puede con una clave externa normal.

¿Cuál es la forma "correcta" de modelar esta relación?

Respuesta

22

Si cree que la aplicación de enlace siempre apuntará a una sola aplicación a continuación, un enfoque sería para pasar el nombre del modelo extranjero como una cadena que contiene la etiqueta de la aplicación en lugar de una referencia de clase (Django docs explanation) .

En otras palabras, en lugar de:

class Link(models.Model): 
    blog_post = models.ForeignKey(BlogPost) 

hacer:

from django.conf import setings 
class Link(models.Model): 
    link_model = models.ForeignKey(settings.LINK_MODEL) 

y en su settings.py:

LINK_MODEL = 'someproject.somemodel' 
+0

Olvidé que django le permite usar nombres de modelo de cadena para esto. +1 – SingleNegationElimination

+0

Oh wow, una gran idea para usar la configuración. ¡Gracias! – Apreche

+0

Tenga en cuenta que este enfoque requeriría que se creen nuevas migraciones en el nivel de aplicación reutilizable. – Bula

0

Probablemente necesite usar la aplicación de tipos de contenido para vincular a un modelo. A continuación, puede organizar que su aplicación compruebe la configuración para realizar comprobaciones adicionales y limitar los tipos de contenido que aceptará o sugerirá.

1

Creo que TokenMacGuy está en el camino correcto. Me gustaría ver cómo django-tagging maneja una relación genérica similar utilizando el tipo de contenido, object_id genérico, and generic.py. De models.py

class TaggedItem(models.Model): 
    """ 
    Holds the relationship between a tag and the item being tagged. 
    """ 
    tag   = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items') 
    content_type = models.ForeignKey(ContentType, verbose_name=_('content type')) 
    object_id = models.PositiveIntegerField(_('object id'), db_index=True) 
    object  = generic.GenericForeignKey('content_type', 'object_id') 

    objects = TaggedItemManager() 

    class Meta: 
     # Enforce unique tag association per object 
     unique_together = (('tag', 'content_type', 'object_id'),) 
     verbose_name = _('tagged item') 
     verbose_name_plural = _('tagged items') 
+0

Sí, específicamente dije que no quería usar el GFK porque entonces no puedo hacer blogpost.objects.all(). select_related ('links') o equivalente. – Apreche

0

Esta pregunta y Van Gale answer me llevan a la pregunta, ¿cómo podría ser posible limitar los tipos de contenido para GFK sin la necesidad de definirlo a través de objetos Q en el modelo, por lo que podría ser completamente reutilizable

la solución se basa en

  • django.db.models.get_model
  • y eval incorporado, que evalúa un Q-Object desde settings.TAGGING_ALLOWED. Esto es necesario para el uso en la administración de interfaz

Mi código es bastante peligroso y no probado plenamente

settings.py

TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb') 

models.py:

from django.db import models 
from django.db.models import Q 
from django.contrib.contenttypes.models import ContentType 
from django.contrib.contenttypes import generic 
from django.db.models import get_model 
from django.conf import settings as s 
from django.db import IntegrityError 

TAGABLE = [get_model(i.split('.')[0],i.split('.')[1]) 
     for i in s.TAGGING_ALLOWED if type(i) is type('')] 
print TAGABLE 

TAGABLE_Q = eval('|'.join(
    ["Q(name='%s', app_label='%s')"%(
     i.split('.')[1],i.split('.')[0]) for i in s.TAGGING_ALLOWED 
    ] 
)) 

class TaggedItem(models.Model): 
    content_type = models.ForeignKey(ContentType, 
        limit_choices_to = TAGABLE_Q)        
    object_id = models.PositiveIntegerField() 
    content_object = generic.GenericForeignKey('content_type', 'object_id') 

    def save(self, force_insert=False, force_update=False): 
     if self.content_object and not type(
      self.content_object) in TAGABLE: 
      raise IntegrityError(
       'ContentType %s not allowed'%(
       type(kwargs['instance'].content_object))) 
     super(TaggedItem,self).save(force_insert, force_update) 

from django.db.models.signals import post_init 
def post_init_action(sender, **kwargs): 
    if kwargs['instance'].content_object and not type(
     kwargs['instance'].content_object) in TAGABLE: 
     raise IntegrityError(
      'ContentType %s not allowed'%(
      type(kwargs['instance'].content_object))) 

post_init.connect(post_init_action, sender= TaggedItem) 

Por supuesto, las limitaciones del contenttype-framework afectan a esta solución

# This will fail 
>>> TaggedItem.objects.filter(content_object=a) 
# This will also fail 
>>> TaggedItem.objects.get(content_object=a) 
manera
1

de Anoher de resolver esto es cómo django-mptt hace esto: sólo definen un modelo abstracto en una aplicación reutilizable (MPTTModel), y requieren a heredar de definir algunos campos (padre = ForeignKey a uno mismo, o lo que su aplicación usecase requerirá)

Cuestiones relacionadas