Na miłość Chrystusa nie używaj monkeypatching do tego. Użyj dziedziczenia, po to jest to.
Wystarczy mieć wtyczki, które wymagają większej liczby pól, aby rozszerzyć istniejące modele, a następnie użyć jednej z wielu technik, aby uzyskać najbardziej wyspecjalizowaną instancję klasy modelu. Używam poniższej klasy jako mixin na wszystkich klasach, które będą używane w ten sposób, aby to osiągnąć.
class Specializable(object):
@classmethod
def all_classes(selfclass):
""" Returns the class this is called on, plus its known subclasses """
subs = selfclass.__subclasses__()
subs.insert(0, selfclass)
return subs
def get_sub_instance(self):
""" Gets concrete instance of object of deepest subtype which has its ancestor link pointing to this object (depth first search behaviour). """
selftype = type(self)
for klass in selftype.__subclasses__():
this_ptr_name = klass._meta.get_ancestor_link(selftype).name
try:
sub = klass.objects.get(**{this_ptr_name: self})
subsub = sub.get_sub_instance()
if subsub: return subsub
else: return sub
except ObjectDoesNotExist:
pass
@classmethod
def new_with_translator(selfclazz, name):
def new(cls, *args, **kwargs):
selfclazz.create_subclass_translator(cls, install = name)
return models.Model.__new__(cls, *args, **kwargs)
return new
@classmethod
def create_subclass_translator(selfclazz, Baseclass, install=None):
""" Creates a classmethod object for installation on Baseclass,
which acts as a factory for instances of subclasses of Baseclass,
when called on that subclass. The factory takes as input an instance
of a subclass (old_instance), and a dictionary of properties with which
to initialise the new instance. It also installs the base class instance
of old_instance as the base instance for the new instance. All three will
share the same pk.
if install is set, this will also install the function on Baseclass under
that name if it does not have a property of that name. """
def create_from_other_instance(selfclass, old_instance, properties):
""" Creates an instance of this class using properties and old_instance.
In particular, it will try to re-use the superclass instance of old_instance.
properties should contain all of the fields for this class (but need not include the superclass values)
This *must* be called on a subclass of the baseclass - it will give odd results if called on the baseclass itself.
This will NOT delete old_instance and it will NOT SAVE the object created - the caller is responsible for
those things."""
if selfclass is Baseclass: raise TypeError("This method cannot be used on the base class")
ancestor_link = selfclass._meta.get_ancestor_link(Baseclass).name
properties.update({ancestor_link: getattr(old_instance,ancestor_link)})
for f in get_model_fields(Baseclass):
val = getattr(old_instance, f)
if val and not properties.get(f):
properties[f] = val
return selfclass(**properties)
new_method = classmethod(create_from_other_instance)
if install and not hasattr(Baseclass, install):
setattr(Baseclass, install, new_method)
return new_method
Oto przykład jego wykorzystania, z niektóre z moich kodu:
#KYCable inherits from Model, so must precede Model
class Director(KYCable, models.Model, Specializable, DateFormatter, AdminURL, Supercedable):
def get_individual(self):
return self.get_sub_instance().get_individual()
Jak widać, Specializable
zajęcia muszą być świadomi, że ich metody mogą być zastąpione przez podklasy, a być kodowane odpowiednio.
Jeśli twoja wtyczka wie o relacjach podklas, to może użyć sztuczek takich jak wyszukiwanie na tym samym identyfikatorze modelu w podklasach, o których wie, aby uzyskać odpowiednią instancję modelu podklasy, gdy jest prezentowana z instancją nadklasy.
Zmiana tabel struktur thru monkeypatch jest IMHO bardzo, bardzo zły pomysł. Zamiast tego możesz użyć GenericForeignKeys lub dowolnego innego "nieniszczącego" rozwiązania ad-hoc. –
To jest bardziej wydajne, nie takie brzydkie, a ja jestem programistą wszystkich tych aplikacji (więc nie ma ryzyka konfliktów bez nadzoru). Nawet jeśli nigdy nie użyłem GenericForeignKeys, ale nie lubię ich. – christophe31
Nie rozumiem problemu. Prawidłowe łatanie małp sprawi, że będzie niezauważalny od południa. Musisz tylko dodać rekurencyjny skrypt migracji. – deufeufeu