Tips: for Initial arg of Django’s Form fields

Django’s Form fields take a initial argument to specify the initial value for that Field.

For more detail about initial, check out the doc Form fields #initial

Basically...

As written there, if you want to set a initial value as a result of a callable, you should pass the callable directly to the argument. like this:

class DateTimeForm(forms.Form):
     now = forms.DateTimeField(initial=datetime.datetime.now)

With arguments

Mistake

Let’s consider the case passing some argument to callable to pass to initial. A common mistake is like this:

# Don't do this
class DateTimeForm(forms.Form):
     now = forms.DateTimeField(initial=datetime.datetime.now(tz))

In this case, the now value would have been fixed at the time you start the server. You should pass a callabse directary, how do you pass arguments to the callable?

Elegant solution

Let’s put together the calling with partial function application. The following is a simple example using functools.partial.

>>> from functools import partial
>>>
>>> zero_until_ten = partial(range, 10)
>>> zero_until_ten()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Yes, you made a callable without arguments. This technique can be used in the previous example, like this:

class DateTimeForm(forms.Form):
     now = forms.DateTimeField(initial=partial(datetime.datetime.now, tz))

The initial will be passed a callable, so the resulting value will not be fixed.

It’s beautiful.

Inelegant solution

Alternatively, you can write like this:

class DateTimeForm(forms.Form):
     now = forms.DateTimeField()

    def __init__(self, *args, **kwargs):
        super(DateTimeForm, self).__init__(*args, **kwargs)
        self.fields.get('now')._set_choices(datetime.datetime.now(tz))

The beauty declarative was lost.

Another develoaper must read __init__, and you should be careful not to forget the calling super.

Not recommended. Use the functools.partial function.

Search view with long long parameters

At most of web application have the so-called search view. The view get some request parameters and display some search results by using these parameters.

A search view does not cause any side effects, so it should expect a GET request. The URL will be like this:

/search?code=R100100&code=R1001001

and the view will be like this:

def search_studends_view(request):
    f = SearchForm(data=request.GET)

    students = None
    if f.is_valid():
        codes = f.cleaned_data['code']
        students = get_students(codes=codes)

    return HttpResponse(",".join(students))

(This is a sample code, so I have not verified the correctness of it)

In most cases, the GET search view seems good and enough. But unfortunately, it have some limitations.

With long long parameter

Let’s consider a searching by using very long parameters. If you should create a view that can be OR search by 1,000 birthdays...

The URL will be like this:

/search?code=R100100&code=R100101&code=R100102&code=R102000&code=...

Then, what would happen? (You may have a doubt the requirement itself)

The header of this request will be very long too. so, It will couse “Request URI too large (414)”.

Gunicorn’s maxcimum size of a request line is 4094 bytes by default.

At Nginx, it is 8k bytes.

If you use reverse proxying, you also should consider settings proxy_buffers, proxy_buffer_size.

だるい。

With POST

Another solution, you can use POST request with search view.

But, if you make the honest, you will run into trouble caused browser history backing. If you back to POST result, most browser will raise warning (like ‘Web page has expired’).

A re-POSTing is handled as bad process to protect such as multiple registration. Also, the POST results will not be cached, because a POST request cause side effect in general. (Some browsers like Chrome, Firefox will cache it. but IE will not so even if you provide Cache-Controll header)

To avoid this, you should redirect to a GET result page after POSTed. POSTed parameter will store to session, and GET page display by using it.

like this:

def search_studends_view(request):
    if request.method == 'POST':
        request.session['search_params'] = request.POST
        return HttpResponseRedirect('/search')
    else:
        try:
            params = request.session['search_params']
        except KeyError:
            params = {}
        f = SearchForm(data=params)

        students = None
        if f.is_valid():
            codes = f.cleaned_data['code']
            students = get_students(codes=codes)

        return HttpResponse(",".join(students))

(Yes, this is a just sample code)

It is not beautiful.

If you want to paginate these results you shuould get a page number from GET parameter (puke).

And more

Serializing and compressing GET parameters on client side may also be good solution.