2016-07-12 3 views
0

Ich habe ein 'Kern' Django-Produkt, das Standardimplementierungen von allgemeinen Aufgaben enthält, aber ich möchte zulassen, dass diese Implementierung neu definiert wird (oder angepasst wird, wenn dies einfacher ist).Wie kann ich eine Funktion in Django neu definieren lassen?

Zum Beispiel in dem Kernprodukt, könnte ich einen Blick habe, die ein Benutzer auf eine Schaltfläche klicken kann erneut zu senden ‚um alle Benachrichtigungen‘:

# in core/views.py 
... imports etc... 
from core.tasks import resend_notifications 

def handle_user_resend_request(request, user_id): 
    user = get_object_or_404(id=user_id) 

    if request.method == 'POST': 
     for follower in user.followers: 
      resend_notifications(follower.id) 

    ... etc etc ... 


# in core/tasks.py 
... imports etc... 

def resend_notifications(id): 
    send_email(User.objects.get(id=id)) 

Und dann in einigen Installationen dieses Produkts, "vielleicht die resend_notifications Bedürfnisse wie folgt aussehen:

# in customer_specific/tasks.py 
... imports etc ... 

def resend_notifications(id): 
    person = User.objects.get(id=id) 
    if '@super-hack.email.com' in person.email: 
     # This is not a real email, send via the magic portal 
     send_via_magic(person) 
    else: 
     send_email(person) 
    # and send via fax for good measure 
    send_fax(person) 

Wie kann ich die resend_notifications Funktion in der views.py Datei auf die customer_specific Version Punkt bekommen?

Sollte ich dies in der Django-Konfiguration definieren und den Zugriff auf diese Weise freigeben? Was, wenn die Aufgaben eigentlich Sellerieaufgaben sind?

NB: Die Aufgaben, die ich habe, sind eigentlich definiert als Sellerie-Aufgaben (Ich entfernte dieses zusätzliche Detail, weil ich denke, die Frage ist allgemeiner). Ich habe versucht mit einem benutzerdefinierten Decorator-Tag, das ein globales Objekt mutiert, aber das ist definitiv nicht der Weg aus einer Reihe von Gründen.

PS: Ich glaube, das ist eine Frage der Abhängigkeitsinjektion, aber das ist in Django nicht üblich.

Antwort

0

Dies wurde über ein Django-Einstellungsobjekt gelöst, das von der Deployment-Konfiguration neu konfiguriert werden kann. Es wurde weitgehend von der Technik hier inspiriert: settings.py from django-rest-framework.

Zum Beispiel, ich habe eine Einstellung wie dies in meinem Projekt-Datei:

IhrProjekt/settings.py

""" 
Settings for <YOUR PROJECT> are all namespaced in the YOUR_PROJECT config option. 
For example your project's config file (usually called `settings.py` or 'production.py') might look like this: 

YOUR_PROJECT = { 
    'PROCESS_TASK': (
     'your_project.tasks.process_task', 
    ) 
} 

This module provides the `yourproject_settings` object, that is used 
to access settings, checking for user settings first, then falling 
back to the defaults. 
""" 
# This file was effectively borrow from https://github.com/tomchristie/django-rest-framework/blob/8385ae42c06b8e68a714cb67b7f0766afe316883/rest_framework/settings.py 

from __future__ import unicode_literals 
from django.conf import settings 
from django.utils.module_loading import import_string 


DEFAULTS = { 
    'RESEND_NOTIFICATIONS_TASK': 'core.tasks.resend_notifications', 
} 


# List of settings that may be in string import notation. 
IMPORT_STRINGS = (
    'RESEND_NOTIFICATIONS_TASK', 
) 


MANDATORY_SETTINGS = (
    'RESEND_NOTIFICATIONS_TASK', 
) 


def perform_import(val, setting_name): 
    """ 
    If the given setting is a string import notation, 
    then perform the necessary import or imports. 
    """ 
    if val is None: 
     return None 
    if callable(val): 
     return val 
    if isinstance(val, (list, tuple)): 
     return [perform_import(item, setting_name) for item in val] 

    try: 
     return import_string(val) 
    except (ImportError, AttributeError) as e: 
     msg = "Could not import '%s' for setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) 
     raise ImportError(msg) 


class YourProjectSettings(object): 
    """ 
    A settings object, that allows settings to be accessed as properties. 
    For example: 

     from your_project.settings import yourproject_settings as the_settings 
     print(the_settings.RESEND_NOTIFICATIONS_TASK) 

    Any setting with string import paths will be automatically resolved 
    and return the class, rather than the string literal. 
    """ 
    namespace = 'YOUR_PROJECT' 

    def __init__(self, mandatory=None, defaults=None, import_strings=None): 
     self.mandatory = mandatory or MANDATORY_SETTINGS 
     self.defaults = defaults or DEFAULTS 
     self.import_strings = import_strings or IMPORT_STRINGS 

     self.__check_settings() 

    @property 
    def user_settings(self): 
     if not hasattr(self, '_user_settings'): 
      self._user_settings = getattr(settings, self.__class__.namespace, {}) 
     return self._user_settings 

    def __getattr__(self, attr): 
     if attr not in self.defaults and attr not in self.mandatory: 
      raise AttributeError("Invalid Pyrite setting: '%s'" % attr) 

     try: 
      # Check if present in user settings 
      val = self.user_settings[attr] 
     except KeyError: 
      # Fall back to defaults 
      val = self.defaults[attr] 

     # Coerce import strings into classes 
     if attr in self.import_strings: 
      val = perform_import(val, attr) 

     # Cache the result 
     setattr(self, attr, val) 
     return val 

    def __check_settings(self): 
     for setting in self.mandatory: 
      if setting not in self.user_settings: 
       raise RuntimeError(
        'The "{}" setting is required as part of the configuration for "{}", but has not been supplied.'.format(
        setting, self.__class__.namespace)) 


yourproject_settings = YourProjectSettings(MANDATORY_SETTINGS, DEFAULTS, IMPORT_STRINGS) 

Dies ermöglicht es mir, entweder:

  • Verwenden Sie die Standardwert (dh 'core.tasks.send_notications '); OR
  • Um die Bindung in meine Config-Datei neu zu definieren:

    site_config/special.py

    ... other django settings like DB/DEBUG/Static files etc 
    
    YOUR_PROJECT = { 
        'RESEND_NOTIFICATIONS_TASK': 'customer_specific.tasks.resend_notifications', 
    } 
    
    ... etc. ... 
    

Dann meiner Ansicht nach Funktion, ich Zugriff auf die korrekte Funktion über die Einstellungen:

Kern/views.py

... imports etc... 
from yourproject.settings import yourproject_settings as my_settings 

def handle_user_resend_request(request, user_id): 
    user = get_object_or_404(id=user_id) 

    if request.method == 'POST': 
     for follower in user.followers: 
      my_settings.RESEND_NOTIFICATIONS_TASK(follower.id) 

    ... etc etc ... 
0

In einer ähnlichen Situation ging ich für eine Lösung wie folgt - ich legte dies auf meine Organization Modell in der Anwendung (equiv einer GitHub Organisation).

@property 
def forms(self): 
    if self.ldap: 
     from portal.ldap import forms 
    else: 
     from portal.users import forms 

    return forms 

Ich wollte wesentlich andere Form Klassen verwenden, wenn die Organisation der authentifizierte Benutzer LDAP konfiguriert gehört hat - und damit die erstellen/laden benötigten Benutzerformulare, anders sein.

Ich überschrieb dann get_form_class in den entsprechenden Ansichten wie so:

def get_form_class(self): 
    return self.request.user.organization.forms.CreateUserForm 

ich denke, Sie könnten etwas ähnliches in Ihrem Szenario tun wollen, wickeln Sie Ihre Funktion (en) in einer Proxy-Abstraktion, welche Version bestimmt, Verwenden Sie dies - basierend auf Umgebungen, Einstellungen oder der Anfrage.

+0

Ich kann definitiv sehen, dass dies eine gute Wahl ist. In meinem Fall muss ich jedoch in der Lage sein, diesen agnostischen Zugang sowohl innerhalb des Kern- als auch des "benutzerdefinierten" Moduls zu haben. Ich kann mir keinen Weg vorstellen, wie dies in den "Kern" integriert werden könnte, ohne dass der Kern explizites Wissen über das benutzerdefinierte Modul erfordert. d. h. ich muss die Funktion 'def forms(): ...' in 'core/tasks.py' setzen, aber dann kann nichts von' customer_specific' importiert werden. – Doddie

+0

Hmm, ich habe deine Antwort noch ein paar Mal gelesen. Ich denke, Sie könnten Recht haben, aber die 'def forms(): ..' Stil-Funktion muss Einstellungen/App-Konfiguration von einer Art verwenden. – Doddie

+0

Sie suchen im Wesentlichen nach dem Abstraktionsmuster auf Python-Modulebene - und die pragmatischste Art und Weise, wie ich selbst dazu gekommen bin, war ein Modul zu schreiben, mit dem ich generisch interagiere, das zwei andere Module (zumindest konzeptionell) enthält) kompatible Schnittstellen. – Steve