13

a newbie django question

I want to use 3rd party app, but I need to make some mods to it (in this case, the app is django-registration, and I need to change things such as allowing registration without confirmation email)

Initially, I simply installed the app in the general site-packages folder, and changed the code there. Now that I put my code on bitbucket I need a way to be able to keep my mods in the repository in a usable way, and uploading the full python code doesn't sound like a good idea.

I guess the best way would be to keep the 3rd party app in site-packages, and create an app in my project to keep my changes. In my case, I'd create my-django-registration in my project, and then import this when I need it in my code, rather than django-registration.

I also read about virtualenv, but I think that's mostly used for being able to use multiple environments in the same machine (in fact, somewhere it advises against changing the modules installed in the virtualenv), and doesn't help me with keeping my changes in a repository.

Any comments welcome! Thanks

xuloChavez
  • 439
  • 6
  • 13

3 Answers3

19

In general, you should reuse and override the behavior in 3rd party apps and not modify their sources.

What you'll most often encounter is that apps ship models that may not necessarily cover your needs, but do most of the work; you'll have forms that are almost perfect, but need that little something; you'll have views that would be perfect if you can just change one thing; you'll have URLs that are sane, but you need something more from them.

In most cases, that would just require that you create a custom app and rewire everything. Ship your own URLs that map to views that you've extended and overrode methods for custom behavior; supply it with a model form who's Meta is using the new model that you've extended from the original; so forth...

That's just the tip of the iceberg of what you can do, there's more ways when your creative. I can give you an example of how I've used the RegistrationProfile model, but shipped my own URL patterns and a custom class-based view that handled the registration process.

Now, where virtualenv comes into play is that you'll most likely be using pip to specify and supply your required dependencies in the requirements file format. That's when you want to say: "I've extended the django-registration app, but it won't work cleanly with just any version. It has to be of release X", or , "a checkout from the repository of commit Y".

Filip Dupanović
  • 32,650
  • 13
  • 84
  • 114
  • 1
    This should be the correct answer. Forking, as suggested by the accepted answer is not the way to go. – mbrochh Dec 31 '12 at 15:30
  • 5
    In some circumstance, forking is necessary. If package is not maintained but it is no longer compatible with latest Django, it is unavoidable. – Imju May 31 '15 at 15:03
12

This blog post that adresses this issue is extremely helpful. For convenience I copy-paste it here:

You don’t touch external app code

You are not supposed to edit the code from an external app. Unless you fork it first on github.

So, how to override without forking:

Overriding a template

If you want to override templates/userena/activate_fail.html, then all you have to do is create your own templates/userena directory and make your own activate_fail.html in it.

Overriding a url

Probably the first thing you should check in an external app is its urls.py. Views that are properly coded should support plenty of arguments. For example, userena has a signup view with such a signature (at the time of writing):

def signup(request, signup_form=SignupForm,
       template_name='userena/signup_form.html', success_url=None,
       extra_context=None):

This means that you can replace the form used by the signup view. To do so, open your urls.py, add what we are going to need at the top:

from userena import views as userena_views
from yourforms import YourSignupForm

Then, find the include the external app’s urls, something like:

url(r'^userena/', include('userena.urls')),

Before that, add your url override:

url(r'^userena/signup/$', userena_views.signup, {'signup_form': YourSignupForm}, name='userena_signup'),
url(r'^userena/', include('userena.urls')),

Now, your custom url definition will be the first to be hit when a visitor hits /userena/signup/. Which means that /userena/signup/ will use YourSignupForm instead of userena’s signup form.

This trick works with any view argument. The ones you should see the most often are:

  • template_name: lets you change the template name
  • extra_context: lets you add a dict that will be added to the context

Almost every view should have these arguments.

Overriding a view

Overriding a view requires to override the url of the view you want to replace. If you want your own signup view to be used, then just override the url:

import yourviews

# ...
url(r'^userena/signup/$', yourviews.yoursignup, name='userena_signup'),
url(r'^userena/', include('userena.urls')),

Decorating a view

Decorating a view is like overriding a view, but reuses the external app’s view. Basically, it’s the same than overriding a view (see above), but your view will look like this

from userena import views as userena_views

def yoursignup(request):
    # do stuff before userena signup view is called

    # call the original view
    response = userena_views.signup(request)

    # do stuff after userena signup view is done

    # return the response
    return response

Forking an app

If you are not familiar with pip and virtualenv first, please read the post about using pip and virtualenv first.

For example:

  • You installed django-userena as such: pip install django-userena
  • First you should uninstall it: pip uninstall django-userena
  • Then go on the app’s github page
  • Click on the fork button
  • This will make you a repository with a copy of django-userena
  • Install it as such: pip install -e git+git@github.com:your-username/django-userena.git#egg=django-userena
  • Then you can edit the code in yourenv/src/django-userena
  • Push your commits

Credits to the post writer!

dimyG
  • 687
  • 9
  • 19
  • that's a very helpful write-up, exactly what I was looking for! – interDist May 21 '19 at 07:41
  • Overriding a template: Seems one thing more is needed: In INSTALLED_APPS your app (with changed template) should be earlier as the 3rd party app (with the original template). – mirek Aug 17 '20 at 12:45
0

I think the neatest way to accomplish what you look for would be to fork django-registration, and in your app use the fork instead of the original project.

That being said, you can have a non-email registration in django-registration without changing the code of the app. I've done it by creating a custom registration backend that sets the users as activated upon creation. Here you can see other ways to do the same.

Community
  • 1
  • 1
Facundo Olano
  • 2,492
  • 2
  • 26
  • 32
  • right, the changes I made are exactly those in the solution by Lionel to the question you link to. By forking do you mean (somehow) creating my own branch of django-registration and make my changes there? I'm new with mercurial too, not sure I can make my own branch on a 3rd party repository, and sounds a bit overkill. I guess having a local app in my project with those changes would achieve the same in a more simple way, although probable less explicit – xuloChavez Dec 08 '11 at 14:40
  • Open source sharing sites such as bitbucket and GitHub provide an option to make a fork (which is as you said a branch of the main project). It's as easy as a couple of clicks. It may be overkill, but I personally see it way less cumbersome than having to copy the entire app inside an unrelated project. Also think that's probable that you'll want to use the same changes in other apps and you'll want to avoid having to copy an paste the modified app each time. – Facundo Olano Dec 08 '11 at 14:50
  • I just tried it and it's indeed a couple of clicks: go to the original repository, click on fork, add your description, and you have you 'copy' of the repository for the fork under your account, where you can make and commit your changes I guess also easily merge any changes from the main trunk. Yep definitely it's a neater solution, thanks! – xuloChavez Dec 08 '11 at 15:47
  • however, how do you indicate in the repository for your main project that it requires a particular fork of an app? (I guess I could also ask in general how do you indicate that it requires a particular app). I guess for this you'd be better off copying the app in your project, but I guess then you lose the connection with the original 3rd party repository – xuloChavez Dec 08 '11 at 15:59
  • You can pip install from a repository url. I've seen the pattern of having a requirements.txt in your main project, with the list of pip commands for installing the dependencies (being app names or repository urls). You should probably use this in conjunction with virtualenv or check for other approaches; I think it doesn't really affect the issue of forking over copying the third party app. – Facundo Olano Dec 08 '11 at 17:37