5

Robię stronę django z wtyczkami. Każda wtyczka jest prostą aplikacją django w zależności od głównej (lub nawet innych wtyczek).Jak małpa patch południe obsługiwane modele z wtyczki?

Podczas gdy zależność między aplikacjami/wtyczkami jest dla mnie jasna, powinno być dopuszczalne dodawanie kolumny (jako klucza obcego do specyficznych modeli wtyczek) poprzez łatanie małpy, aby uniknąć głównej aplikacji zależnej od wtyczki.

Ponieważ główna aplikacja ma już zarządzanie na południe, a więc wszystkie wtyczki, nie mogę zmienić katalogu migracji w ustawieniach dla tych modułów.

A więc, w jaki sposób mogę załatać małpki model aplikacji południowej z innej aplikacji na południe?

ps: Jestem Francuzem, nie krępuj się poprawić moje pytanie, jeśli zauważysz błąd lub zapytasz o nic, jeśli nie jestem pewien.

Edytuj: Dodałem odpowiedź na temat tego, jak teraz wykonuję migrację django.

+2

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. –

+0

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

+0

Nie rozumiem problemu. Prawidłowe łatanie małp sprawi, że będzie niezauważalny od południa. Musisz tylko dodać rekurencyjny skrypt migracji. – deufeufeu

Odpowiedz

3

Na razie moim najlepszym rozwiązaniem było utworzenie własnego pliku migracji w plug-inie (co sugeruje dodawanie tabel w słowniku modeli pliku migracji).

Zobaczę później w następnej migracji, jeśli wszystkie modele będą automatycznie śledzone.

W moim nowym pliku migracji:

class Migration(SchemaMigration): 
    def forwards(self, orm): 
     db.add_column(u'remoteapp_model', 'fieldname', 
         self.gf('django.db.models.fields.related.ForeignKey', 
         (to=orm["my_plugin.MyModel"], default=None, null=True, blank=True), 
         keep_default=False) 


    def backwards(self, orm): 
     db.delete_column(u'remoteapp_model', 'fieldname') 

    # for models, you may want to copy from a previous migration file 
    # and add from the models of the main application the related tables 
    models = {} 

W moim pliku modele:

from remoteapp.models import RemoteModel 
from django.db import models 

class MyModel(models.Model): 
    pass 

models.ForeignKey(MyModel, null=True, blank=True, 
        default=None).contribute_to_class(RemoteModel, 'my_model') 
0

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.

+0

Co tworzy nowy stół, wymaga sprzężeń, zmniejsza wydajność, jest mniej praktyczne, wymaga więcej kodu ... Nie przekonujesz mnie, że "łatanie małp jest brzydkie", nie podałeś żadnych argumentów. Ale otrzymałeś nagrodę, ponieważ tylko ty odpowiedziałeś. Czytasz to pytanie i odpowiadasz na pytanie, na które chciałeś odpowiedzieć, a nie na pytanie, które zadałem. Ale dzięki za odpowiedź, która jest dobrze skonstruowana. – christophe31

+0

@ christophe31 Jest masowo bardziej praktyczny, ponieważ używasz narzędzia, ponieważ ma ono być używane, więc unikaj wprowadzania trudnych do znalezienia błędów, a także unikaj konieczności ręcznego pisania własnych migracji. Nie ma na mnie obowiązku mówienia ci, jak zrobić coś strasznego. Monkeypatching jest dla dynamicznego systemu obiektowego. Po odwzorowaniu tego na relacyjną bazę danych, system statycznych klas jest reprezentowany w pamięci przez dynamiczny system obiektowy. Czy zastanawiałeś się również, w jaki sposób poradzisz sobie z dwiema wtyczkami, które dodają tę samą nazwę pola? Dziedziczenie po raz kolejny pozwala uniknąć tego problemu. – Marcin

+0

@ christophe31 Jeśli chodzi o twoją opinię na temat wydajności - przedwczesna optymalizacja jest źródłem wszelkiego zła. – Marcin

1

Po migracji do django 1.7 i migracji django, nowa odpowiedź nie była oczywista, musiałem utworzyć własną klasę migracji, aby dodać klucz obcy do tabeli zdalnej.

from django.db.migrations import AddField 

class AddRemoteField(AddField): 
    def __init__(self, remote_app, *args, **kwargs): 
     super(AddRemoteField, self).__init__(*args, **kwargs) 
     self.remote_app = remote_app 

    def state_forwards(self, app_label, *args, **kwargs): 
     super(AddRemoteField, self).state_forwards(self.remote_app, *args, **kwargs) 

    def database_forwards(self, app_label, *args, **kwargs): 
     super(AddRemoteField, self).database_forwards(
      self.remote_app, *args, **kwargs) 

    def database_backwards(self, app_label, *args, **kwargs): 
     super(AddRemoteField, self).database_backwards(
      self.remote_app, *args, **kwargs) 

I wtedy zrobić plik migracji:

from __future__ import unicode_literals 

from django.db import models, migrations 
from my_app.tools import AddRemoteField 
from my_app.models import Client 


class Migration(migrations.Migration): 

    dependencies = [ 
     ('anikit', '0002_manual_user_migration'), 
    ] 

    operations = [ 
     AddRemoteField(
      remote_app='auth', 
      model_name='user', 
      name='client', 
      field=models.ForeignKey(Client, verbose_name='client', 
            null=True, blank=True), 
      preserve_default=True, 
     ), 
    ] 
Powiązane problemy