2009-09-14 14 views
12

Nel mio sito django ho due app, blog e collegamenti. il blog ha un blog post modello e i link hanno un collegamento modello. Ci dovrebbe essere una relazione uno a molti tra queste due cose. Ci sono molti link per blogpost, ma ogni link ha un solo ed unico post sul blog. La semplice risposta è mettere un ForeignKey per blogpost nel modello di link.Come modellare una chiave esterna in un'app Django riutilizzabile?

Questo va tutto bene, tuttavia, c'è un problema. Voglio rendere l'app dei collegamenti riutilizzabile. Non voglio che dipenda dall'app del blog. Voglio poterlo riutilizzare in altri siti e magari associare link con altre app e modelli non di postazione blog.

Una chiave straniera generica sembra che potrebbe essere la risposta, ma non proprio. Non voglio che i collegamenti siano in grado di associarsi a nessun modello nel mio sito. Solo quello che specifichi esplicitamente. E so per esperienza precedente che ci possono essere problemi nell'utilizzo di chiavi esterne generiche in termini di utilizzo del database perché non è possibile eseguire un select_related su una chiave esterna generica come è possibile con una normale chiave esterna.

Qual è il modo "corretto" per modellare questa relazione?

risposta

22

Se si pensa che l'applicazione di collegamento sarà sempre puntare a una singola applicazione poi un approccio sarebbe quello di passare il nome del modello straniera come una stringa contenente l'etichetta applicazione invece di un riferimento di classe (Django docs explanation) .

In altre parole, invece di:

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

fare:

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

e nel tuo settings.py:

LINK_MODEL = 'someproject.somemodel' 
+0

Avevo dimenticato che Django ti permette di usare i nomi dei modelli di stringa per questo. +1 – SingleNegationElimination

+0

Oh wow, ottima idea di usare le impostazioni. Grazie! – Apreche

+0

Si noti che questo approccio richiede la creazione di nuove migrazioni a livello di app riutilizzabile. – Bula

0

Probabilmente è necessario utilizzare l'app dei tipi di contenuto per collegarsi a un modello. Potresti quindi fare in modo che la tua app verifichi le impostazioni per effettuare alcuni controlli aggiuntivi per limitare i tipi di contenuto che accetterà o suggerirà.

1

Penso che TokenMacGuy sia sulla strada giusta. Vorrei vedere come django-tagging gestisce una relazione generica simile usando il tipo di contenuto, oggetto_id generico, and generic.py. Da 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ì, ho detto in particolare che non volevo utilizzare GFK perché quindi non posso fare blogpost.objects.all(). select_related ('links') o equivalente. – Apreche

0

Questa domanda e Van Gale di answer mi portano alla domanda, come potrebbe essere possibile limitare il contenttypes per GFK senza la necessità di definirlo tramite oggetti Q nel modello, quindi potrebbe essere completamente riutilizzabile

la soluzione è basata su

  • django.db.models.get_model
  • e l'eval integrato, che valuta un oggetto Q da settings.TAGGING_ALLOWED. Ciò è necessario per l'utilizzo nel admin-interfaccia

Il mio codice è abbastanza agitato e non completamente testato

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) 

Naturalmente le limitazioni del contenttype-framework influenzano questa soluzione

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

Anoher modo per risolvere questo è il modo in django-mptt fa questo: solo definire un modello astratto in un'applicazione riutilizzabile (MPTTModel), e richiedono di ereditare con la definizione di alcuni campi (genitore = ForeignKey a sé, o qualunque sia la vostra applicazione usecase richiederà)

Problemi correlati