2008-05-27

Django Meetup at Linuxtag

Pssst. Don't tell anybody! There is an unofficial Django user meetup happening in Berlin this Friday at five.

datetime.datetime(2008, 5, 27, 17, 0)


The gathering point is at the main entrance of Linuxtag conference (Messe Berlin).

2008-05-23

Decorating the Render Methods of New-Form Widgets

Perhaps all template masters have already faced the problem of styling HTML input fields of different types. The selectors like input[type=checkbox] and similar in CSS are not supported by IE so people working with templates and CSS obviously need some other way to select and style specific types of input fields.

There are a few ugly ways to do that which violate the DRY principle:

  • Encompassing the form element in the template with HTML tag which has a class representing specific type of the input field.
    For example:
    <span class="form_checkbox">
    {{ form.is_privacy_policy_confirmed }}
    </span>


  • Defining the specific CSS class for each form field widget in the form.
    For example:
    class FormExample(forms.Form):
    is_privacy_policy_confirmed = forms.BooleanField(
    required=True,
    widget=CheckboxInput(attrs={'class': 'form_checkbox'}),
    )


  • Extending all Fields and all Widgets which use HTML input fields and using the extended versions instead of the originals.



I don't like any of them, because they force me or the template formatters to repeat ourselves and make plenty of replacements in our existing forms.

Although "most sane developers consider it a bad idea", I see the Guerrilla patching of the Widget's render method as the nicest solution to solve this problem.

Guerrilla patch is the modification of the runtime code in dynamic languages without changing the original source code.

The method render of the Widget class draws the input field in HTML. As it takes a parameter attrs for additional input field attributes, my idea was to create a decorator which modifies the incoming parameters and adds a CSS class "form_TYPE", where TYPE is the input field type.

With a little help by Dalius Dobravolskas, I succeeded to code a decorator having an optional parameter which defines the CSS class name for the field. If the class name is not defined, the attribute input_type of the Widget class is used for forming the CSS class name (N.B. not all widgets have this attribute).

from django.newforms.widgets import Input, CheckboxInput, RadioSelect, CheckboxSelectMultiple

### adding class="form_*" to all html input fields ###
def add_css_class(css_class=""):
def modify_input_class(function):
_css_class = css_class
def new_function(*args, **kwargs):
arg_names = function.func_code.co_varnames
new_kwargs = dict(zip(arg_names, args))
new_kwargs.update(kwargs)
css_class = _css_class or "form_%s" % getattr(
new_kwargs['self'],
"input_type",
"undefined",
)
self = new_kwargs.pop("self")
attrs = getattr(self, "attrs", None) or {}
if "class" in attrs:
css_classes = attrs["class"].split()
if css_class not in css_classes:
css_classes.append(css_class)
attrs["class"] = " ".join(css_classes)
else:
attrs["class"] = css_class
self.attrs = attrs
return function(self, **new_kwargs)
return new_function
return modify_input_class
Input.render = add_css_class()(Input.render)
CheckboxInput.render = add_css_class("form_checkbox")(CheckboxInput.render)
RadioSelect.render = add_css_class("form_radio")(RadioSelect.render)
CheckboxSelectMultiple.render = add_css_class("form_checkbox")(CheckboxSelectMultiple.render)


To use this code, just place it in some models.py file in your project.

The strange part here was that the variable css_class isn't recognized by the sub-child function new_function directly although the scope of the variable css_class should let it be accessed there. Anyway, the value got easily accessible when I reassigned it to another variable like _css_class in the child function modify_input_class.

The tricky part of this snippet was getting the attrs argument from the decorated function as it was not clear whether it would be passed as a positional or as a named argument. The first three lines of the function new_function collects all the incoming arguments to a dictionary new_kwargs. They can be modified and then passed to the original function to decorate.

Ups. I am late to the studio. So see you next time!

2008-04-08

Tips #2

What time is it now? It's time to give you some more tips about Django development!

Use batch scripts to automate manual routines. Do not repeat yourself extracting and compiling translatable strings, starting and stopping development web-servers, updating and committing your project to the version-control system in the console. Write batch scripts which you can run within one mouse click instead.

Define overwritable constants in your applications. Your applications are likely using some values that might be defined as constant values, i.e. the dimensions for avatars of users. Define those constants so, that you could overwrite them in the project settings if necessary.
from django.conf import settings
SOME_SETTING = getattr(settings, "SOME_SETTING", "default value")


Have one view for site-related JavaScript globals. Django views usually return (X)HTML-based responses, but it can return XML, JavaScript or others as well. Usually you will hold all you JavaScript functions in static files, but there might be some situation, where you need to get information related to database or project settings, for example, the MEDIA_URL.
The following view might be used to display a javascript page directly from a template. Just pass the template to the view in your urls.py file.
from datetime import datetime, timedelta
from django.views.generic.simple import direct_to_template
def direct_to_js_template(request, *args, **kwargs):
response = direct_to_template(request, *args, **kwargs)
response['Content-Type'] = "application/x-javascript"
now = datetime.utcnow()
response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
expires = now + timedelta(0, 2678400)
response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
return response


And now it's time to go home and to get relaxed.

2008-03-24

DRY While Working With Choices for Forms

When creating dozens of forms with selection fields for some many-to-one or many-to-many relations, you might find that it's ineffective to create choices for the form fields from querysets formed by the relations defined by ForeignKeys and ManyToManyFields. You have to import the related models, filter the choices analogously to the limit_choices_to parameter, and form a list of tuples again and again.

To get the same choices from the model as in the admin form, you can use the following:
FIELD_CHOICES = SomeModel._meta.get_field("field_name").get_choices()

Then you can modify the text for the null-value choice, like
FIELD_CHOICES[0] = ("" _("- Choose One -"))

or even remove it:
del FIELD_CHOICES[0]

To save the selected object you can simply assign the chosen value to the foreign key, like:
new_instance = SomeModel()
new_instance.field_name_id = form.cleaned_data['field_name']
new_instance.save()

If you need to do something with the selected object, you can still live without importing specific models and filtering the entries in the same manner as limit_choices_to. To save time, you can use the following function, which returns the queryset containing all the choosable objects:
def get_related_queryset(model, field_name):
"""
Get the queryset for the choices of the field in a model
Example:
objects = get_related_queryset(SomeModel, "field_name")
"""
f = model._meta.get_field(field_name)
qs = f.rel.to._default_manager.complex_filter(f.rel.limit_choices_to)
return qs

Just put this function in one of you applications and import it whenever you need to work with forms. And have happy Easter!

2008-03-04

Hacking Contributed Models

Django comes with a bunch of contributed applications like auth for authentication, admin for basic data manipulation, or flatpages for the dynamic content that tends to be rarely changed. Sometimes you want to use them, but their functionality doesn't completely fit your needs.

You have several options in that case:
  • Use custom models with one-to-one or many-to-one relations, creating extensions for existing models. The main drawback of this approach is that those custom models will be editable in the contributed administration separately from the origin or you will need to create custom views to combine everything nicely.
  • Use modified duplicates instead of the contributed applications. The main drawback of this case is that it will be hard to manage the updates of the models.
  • Use signals to modify the models. This can be too complicated for simple changes.
  • Modify the properties of the models on the fly.
Let's dig deeper into the last option. When you start the shell or request for a page on the web server, at first Django loads all modules according the order of the INSTALLED_APPS setting. As Python is an interpreted language and all methods and properties are public, you can access and change them on the fly.

For example, if your task is to rename verbose name of groups to "Roles" and show related users at the list view of contributed administration, then it can be achieved by the following code in some model which application appears somewhere below the "django.contrib.auth" in the INSTALLED_APPS.
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group

def modify_group_class():
""" A function modifying the contributed Group model """
# modifying the verbose names
Group._meta.verbose_name = _("Role")
Group._meta.verbose_name_plural = _("Roles")

def display_users(group):
""" A function displaying users for a group """
links = []
for user in group.user_set.all():
links.append(
"""<a href="/admin/auth/user/%d">%s</a>""" % (
user.id,
("%s %s" % ( # show the real name
user.first_name,
user.last_name, # or the username
)).strip() or user.username,
)
)
return "<br />".join(links)
display_users.allow_tags = True
display_users.short_description = _("Users")

# attaching the new function to the Group model
Group.display_users = display_users

# changing the list_display for the Group model
Group._meta.admin.list_display = ("__str__", "display_users")

modify_group_class()


As you might see from this example, Group._meta returns the Meta class and Group._meta.admin returns the Admin class for the Group model. You can prove it to yourself by browsing objects in Django shell aka Python IDE.

Use this kind of hacking whenever you reasonably need to change Django core functionalities or some third party modules and have no other choice. However, don't overuse it, 'cause it might lead to difficulties in maintaining the project.

2008-03-03

Tips #1

As I can't get to sleep after my last mug of strong black tea, I decided to write my first tips which will make your life easier.

Use consistent and readable coding convention. You know, class names start with capital letters, the variables and functions with small ones. The different words should be connected either using camelCase or by_underscores, but not mixed up in one project. Use triple double quotes """for documentation of functions""" and triple single quotes '''for commenting some code out'''. For readability it is convenient to limit all your lines to 80 symbols. If a statement is very long, put it inside the brackets and then separate into different lines (this is easier to manage than having a backslash (\) before the new line symbol). When debugging and adding print statement or similar, also add some comment like # DEBUG, which can be easily found later. If there is something to improve in the code, add a comment like # TODO with the description of possible improvement. These words are easily findable later. If you have a long list, tuple or dictionary of items, or a function with many arguments which don't fit into the limit of 80 symbols, separate the items with each per line, indented by one tab. Use comma even after the last item unless it is **kwargs. Put the closing bracket after all. This will let you easily rearrange the order or amount of items later.
view = my_special_view(
request,
arg1,
arg2,
arg3,
)


Access items in administration faster. Instead of browsing through links and loading unnecessary pages before getting to the necessary ones, type in the URI of the required model directly. All lists of objects can be accessed by /admin/<app_name>/<model_name_in_lower_case>/

Use Python IDE and read Django source. Learn and use the interactive development environment, because it is usually a faster (and more attractive) way to learn how the functions work, than writing the code to the files first and checking the results in the browser afterwards. Use the dir() function for all objects to check the available methods and attributes, use the __dict__ attribute for model instances to check its field values, write for-loops for listing out specific attributes from model instances from different querysets. Interaction and visual representation always gives you some better understanding about the architecture than just Django documentation. Also when something is not clear, don't be afraid to read the source. Take advantage of Django being an open source project.
>>> from django.contrib.auth.models import User
>>> for u in User.objects.all():
... u.__dict__
...
{'username': u'aidas', 'first_name': u'Aidas', 'last_name': u'Bendoraitis', 'is_active': 1, 'email': u'', 'is_superuser': 0, 'is_staff': 0, 'last_login': datetime.datetime(2008, 2, 27, 13, 43, 58), 'password': u'sha1$e55b5$9aa9485f4ad6fc7947e80b63e6d56519c73ac5bb', 'id': 1L, 'date_joined': datetime.datetime(2007, 3, 30, 20, 56, 7)}


That's it for this time. Now it's really time to sleep. :D

2008-03-02

HelloWorld.py

Hello Pythoners! Hello Djangoers! Hello other people!

This is yet another blog about Django - the Web framework for perfectionists with deadlines.

I decided to start writing it to log some knowledge that comes from practice of daily work with Django. It's useful to have it for later reference and also for sharing it with you. And you are welcome to comment my code, correct where I am wrong, and open discussions.

If you are not much familiar with Django, but would like to learn it, then I suggest you to read the following resources at first:

Have a nice day.