3

Let's have a django project using a 3rd party application. I'd like to override some of its modules without touching original files. Simple subclassing is not possible here, need to override code transparently as many other apps rely on original class names and functions.

Project's structure looks like:

django_project/
  __init__.py
  settings.py
  overrides/        <-- here is a subdir with apps overrides
    __init__.py
    payment/        <-- here is an example of app to override
      __init__.py
      admin.py
      forms.py      <-- this file is ignored, original is imported
      models.py
      tests.py
      views.py

settings.py was modified with

INSTALLED_APPS=(
    'satchmo_store.shop'
    #'payment'           # original values
    'overrides.payment'  # modified app
    ...
)

The above solution however does not work, because Django does not insert path of added app into modules search path (sys.path). Django just loads admin.py, models.py, tests.py and views.py, other files like forms.py are ignored.

Is this behaviour documented somewhere ? What exactly placing a module name in INSTALLED_APPS does behind scenes ?

I hacked the situation with hardcoding new modules search path in manage.py and Apache's setting of WSGIPythonPath.

import os.path
import sys

DIRNAME       = os.path.dirname(__file__)
APPS_OVERRIDE = os.path.join(DIRNAME, 'overrides')

if not APPS_OVERRIDE in sys.path:
    sys.path.insert(1, APPS_OVERRIDE)

I doubt this is the right way. Cann't find a guide describing apps overriding.

So, how can I properly override external Django application in my project ?

The bonus question: Do I need to copy whole application directory tree, not just particular files which are really modified ? As far as I know, Python stops at first matching module path, so it won't import other modules available in following parts of the search path.

David Unric
  • 7,421
  • 1
  • 37
  • 65
  • 1
    Properly overriding... fork the project unless there is a hook in the project somewhere. The easy fix (which isn't always possible), monkeypatching by changing the code after import. As far as I know there is no documentation about the behaviour but the rule of thumb for me is always, if you want to override smoething, do it in the `models.py` since that always gets imported as the first part. – Wolph Dec 15 '13 at 11:21
  • @Wolph So how do I have to apply `overrides.payment.form` to transparently replace the original `payment.form` ? Monkey patch whole module in `overrides.payment.module` ? – David Unric Dec 15 '13 at 12:32
  • I've added an example as an answer, try and play around with that :) – Wolph Dec 15 '13 at 12:56
  • I agree with the idea of forking, it's too much trouble otherwise. Or be a good citizen and suggest the hook you need via a pull request. What I see here is a technical solution to an organizational problem, and it never ends well ;-) – vincent Dec 15 '13 at 22:33

2 Answers2

1

Example of how to override your form:

overrides/payment/forms.py

from django import forms
class YourNewFormThingy(forms.Form): pass

overrides/payment/models.py

from satchmo.payment import forms as satchmo_payment_forms
from . import forms

satchmo_payment_forms.SomeForm = forms.YourNewFormThingy
Wolph
  • 78,177
  • 11
  • 137
  • 148
  • I did intended to avoid hacking like monkey-patching, but if there won't be a cleaner solution from somebody else, you'll get the bounty. – David Unric Dec 15 '13 at 19:36
  • You can try hacking around it by hacking imports, but it's less reliable and more confusing in my opinion – Wolph Dec 15 '13 at 20:31
  • Ok, you've earned the bounty. Congrats. – David Unric Dec 16 '13 at 13:02
  • @DavidUnric: Thanks :) Alternatively you could also consider forking satchmo to add the hooks and submitting it as a pull request. If it's reasonable than I'm sure they'll merge it. – Wolph Dec 16 '13 at 13:20
0

Try including payment as well along with override.payment as satchmo uses payment module to process payments and payment code is flexible enough to include your code as well.

Arpit
  • 953
  • 7
  • 11
  • I did included both, however if I omit `sys.path` injection in `manage.py`, I'm getting Importerror as my payment app loads modules from original payment app tree. In addition `overrides.payment.form` is not loaded. – David Unric Dec 15 '13 at 12:29
  • Do you want to modify the original form provided by `payment` module? If so, I would suggest you to use Wolph's solution. I thought you implemented your own processor. – Arpit Dec 15 '13 at 18:30