2010-03-30 4 views
7

Comme dans this question, sauf que je veux être en mesure d'avoir QuerySets qui retournent un corps mixte d'objets:modèles subclassed django avec QuerySets intégrés

>>> Product.objects.all() 
[<SimpleProduct: ...>, <OtherProduct: ...>, <BlueProduct: ...>, ...] 

je me suis dit que je ne peux pas mettre Product.Meta.abstract true ou sinon juste OU ensemble des ensembles de différents objets. Bien, mais ce sont tous des sous-classes d'une classe commune, donc si je laisse leur superclasse comme non-abstraite, je devrais être heureux, tant que je peux obtenir que son manager retourne des objets de la classe appropriée. Le code de la requête dans django fait son truc, et fait juste des appels à Product(). Cela semble assez facile, sauf qu'il explose quand j'Override Product.__new__, je devine à cause du __metaclass__ dans le modèle ... Voici le code non-django qui se comporte à peu près ce que je veux:

class Top(object): 
    _counter = 0 
    def __init__(self, arg): 
     Top._counter += 1 
     print "Top#__init__(%s) called %d times" % (arg, Top._counter) 
class A(Top): 
    def __new__(cls, *args, **kwargs): 
     if cls is A and len(args) > 0: 
      if args[0] is B.fav: 
       return B(*args, **kwargs) 
      elif args[0] is C.fav: 
       return C(*args, **kwargs) 
      else: 
       print "PRETENDING TO BE ABSTRACT" 
       return None # or raise? 
     else: 
      return super(A).__new__(cls, *args, **kwargs) 
class B(A): 
    fav = 1 
class C(A): 
    fav = 2 
A(0) # => None 
A(1) # => <B object> 
A(2) # => <C object> 

Mais cela échoue si je hérité de django.db.models.Model au lieu de object:

File "/home/martin/beehive/apps/hello_world/models.py", line 50, in <module> 
    A(0) 
TypeError: unbound method __new__() must be called with A instance as first argument (got ModelBase instance instead) 

Ce qui est un backtrace notamment merdique; Je ne peux pas entrer dans le cadre de mon code __new__ dans le débogueur, soit. J'ai essayé diversement super(A, cls), Top, super(A, A), et tout ce qui précède en combinaison avec le passage cls en tant que premier argument à __new__, le tout en vain. Pourquoi est-ce que ça me donne des coups de pied si durs? Est-ce que je dois comprendre les métaclasses de Django pour pouvoir résoudre ce problème ou existe-t-il un meilleur moyen d'atteindre mes objectifs?

+1

Il est tentant d'essayer de comprendre le casse-tête, mais l'instinct me dit que vous faites fausse route. C'est comme une torture pour le pauvre petit ORM de Django. – keturn

Répondre

4

Fondamentalement, ce que vous essayez de faire est de retourner les différentes classes enfant, tout en interrogeant une classe de base partagée. Autrement dit: vous voulez les classes de feuilles.Vérifiez cet extrait pour une solution: http://www.djangosnippets.org/snippets/1034/

Assurez-vous aussi de consulter la documentation sur le cadre ContentTypes de Django: http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/ Il peut être un peu déroutant au début, mais ContentTypes résoudra des problèmes supplémentaires que vous aurez probablement confrontés lors de l'utilisation non classes de base abstraites avec ORM de Django.

+0

mise à jour pour aucune utilisation de '__new__' ou' __metaclass__'. – keturn

+0

Le lien de l'extrait est cassé. – freyley

1

D'accord, cela fonctionne: https://gist.github.com/348872

le plus délicat était cela.

class A(Top): 
    pass 

def newA(cls, *args, **kwargs): 
    # [all that code you wrote for A.__new__] 

A.__new__ = staticmethod(newA) 

Maintenant, il y a quelque chose sur la façon dont Python lie __new__ que je peut-être ne comprends pas tout à fait, mais l'essentiel de c'est ceci: ModelBase métaclasse de django crée un nouvel objet de classe, plutôt que d'utiliser celui qui est passé dans à son __new__; appelez cela A_prime. Puis il colle tous les attributs que vous aviez dans la définition de classe pour A sur A_prime, mais __new__ ne se re-lié correctement.

Puis, quand vous évaluez A(1), A est en fait A_prime ici, appelle python <A.__new__>(A_prime, 1), qui ne correspond pas, et il explose.

Donc la solution est de définir votre __new__aprèsA_prime a été défini.

Peut-être que c'est un bug dans django.db.models.base.ModelBase.add_to_class, peut-être que c'est un bug en Python, je ne sais pas. Maintenant, quand j'ai dit "ça marche" plus tôt, je voulais dire cela fonctionne de manière isolée avec le cas de test de construction d'objet minimal dans la version SVN actuelle de Django. Je ne sais pas si cela fonctionne réellement comme un modèle ou est utile dans un QuerySet. Si vous l'utilisez réellement dans le code de production, j'en ferai un exposé public éclair pour pdxpython et je vous demanderai de vous moquer de vous jusqu'à ce que vous nous achetiez toutes les pizzas sans gluten.

+0

Heh. Eh bien, ça me donne le comportement de sous-classement nécessaire, mais oh, ça brise toutes mes relations! Aucun de mes objets ne peut alors se rapporter à d'autres modèles. Je ferai des recherches plus loin pour voir si je peux réparer cet aspect. – outofculture

1

Collez simplement @staticmethod avant la méthode __new__.

@staticmethod 
def __new__(cls, *args, **kwargs): 
    print args, kwargs 
    return super(License, cls).__new__(cls, *args, **kwargs) 
Questions connexes