2014-12-30

How Django shows the “It worked!” page?

If you do the following, you will see a “It worked!” page at http://127.0.0.1:8000

$ django-admin.py startproject myproject
$ python manage.py runserver

Let's find out how it is done.

First search the django package to find the phrase “It worked!”. Assuming you are at the root of the django package:

$ grep -ri "it worked!" --include=*.py .
./views/debug.py:  <h1>It worked!</h1>

Look into debug.py and you will see “It worked!” is defined in the string “DEFAULT_URLCONF_TEMPLATE”.

“DEFAULT_URLCONF_TEMPLATE” is used in a module level function “default_urlconf()” in the same file.

The “default_urlconf()” function is called by another module level function “technical_404_response()” in the same file.

Portion of “technical_404_response() is listed below:

    try:
        tried = exception.args[0]['tried']
    except (IndexError, TypeError, KeyError):
        tried = []
    else:
        if (not tried # empty URLconf
            or (request.path == '/'
                and len(tried) == 1 # default URLconf
                and len(tried[0]) == 1
                and getattr(tried[0][0], 'app_name', '') == 
                        getattr(tried[0][0], 'namespace', '') == 'admin')):
            return default_urlconf(request)

You can see that it checks for the special case of hitting the root url “/” and filtering out the admin site.

Let's do a search on technical_404_response():

$ grep -ri "technical_404_response" --include=*.py .
./views/debug.py:def technical_404_response(request, exception):
./contrib/staticfiles/handlers.py:   return debug.technical_404_response(request, e)
./core/handlers/base.py:             response = debug.technical_404_response(request, e)

The relevant file is “base.py”. In the definition of class BaseHandler(object) in base.py, if the DEBUG setting is True, then it will call technical_404_response() and eventually returns the “It worked!” page, as illustrated in the code fragment below.

        except http.Http404 as e:
            logger.warning('Not Found: %s', request.path,
                        extra={
                            'status_code': 404,
                            'request': request
                        })
            if settings.DEBUG:
                response = debug.technical_404_response(request, e)

2014-12-23

How to install a python package for one particular webapp in WebFaction

Both of the following examples are based on a Django 1.7 / Python 3.4 app.

Example 1: ReportLab
$ pip3.4 install --user -U setuptools
$ cd ~/webapps/APP
$ PYTHONPATH=$PWD/lib/python3.4 pip3.4 install \
  --install-option="--install-scripts=$PWD/bin" \
  --install-option="--install-lib=$PWD/lib/python3.4" \
  reportlab==3.1.8

$ python3.4 manage.py shell
>>> import reportlab
>>> reportlab.__file__ # to verify
Example 2: django-taggit
$ pip3.4 install --user -U setuptools
$ cd ~/webapps/APP
$ PYTHONPATH=$PWD/lib/python3.4 pip3.4 install -t $PWD/lib/python3.4 \
  django-taggit

$ python3.4 manage.py shell
>>> import taggit
>>> taggit.__file__ # to verify
The exact method to use depends on how the package is built.

2014-12-16

Ever wondering what does {% load staticfiles %} mean?

It means Django will loop through all INSTALLED_APPS directory to look for a “templatetags” subdirectory.

If found, it will in turn look for a “staticfiles.py” file inside the templatetags subdirectory.

If staticfiles.py is found, then it will import it.

For example, if django.contrib.staticfiles is in the INSTALLED_APPS, then
{% load staticfiles %}
will import the following file:
django/contrib/staticfiles/templatetags/staticfiles.py
There is a module level function called “static” defined in staticfiles.py, which is the “static” in the {% static %} template tag.

2014-12-02

How Django deals with methods renaming

A RenameMethodsBase metaclass is defined in the django.utils.deprecation module. It seems that Django uses it to deal with methods renaming. Below is a simplified explanation of how it works.

First we subclass the RenameMethodsBase to specify the old and new method names, as well as the type of warning. Then we use it as a metaclass on two classes Field1 and Field2.
import warnings
warnings.simplefilter("always")

from django.utils.deprecation import *

class RenameFieldMethods(RenameMethodsBase):
    renamed_methods = (
        ('_has_changed', 'has_changed', DeprecationWarning),
    )

class Field1(metaclass=RenameFieldMethods):
    def _has_changed(self):
        print('_has_changed')

class Field2(metaclass=RenameFieldMethods):
    def has_changed(self):
        print('has_changed')
If we instantiate Field1 and call both old and new methods, the following results:
>>> f1 = Field1()
>>> f1.has_changed()
_has_changed
>>> f1._has_changed()
__main__:1: DeprecationWarning: `Field1._has_changed` is deprecated, use `has_changed` instead.
_has_changed
On the other hand if we instantiate Field2 and call both old and new methods, the following results:
>>> f2 = t2.Field2()
>>> f2.has_changed()
has_changed
>>> f2._has_changed()
__main__:1: DeprecationWarning: `Field2._has_changed` is deprecated, use `has_changed` instead.
has_changed
In short, both methods will succeed, but if called by the name of the old method, a warning is issued.