• Skip to main content

zoiaorg

Tech, Strategy, and other interesting things by Roberto Zoiaby Roberto Zoia

  • Articles
  • Book Reviews
  • Effective Goal Setting
  • What I’m Reading
  • Archive
  • About

django

Google App Engine

2008-04-09 by Roberto Zoia

Hace unos días Google lanzó Google App Engine. Me ahorro detalles que ya se han comentado en otros sitios.
El video de abajo muestra una aplicación sencilla. (No es ninguna novedad, lo saqué de la página de Google App Engine.) El framework, en este caso, no es Django (sí usa los templates de Django), pero a los que hayan programado con Django el código les resultará algo familiar.

Filed Under: Legacy Tagged With: app_engine, django, web_framework, web_programming

Free the Dragon!

2008-03-08 by Roberto Zoia

Hace unos días ActiveState ha sacado una versión open source de Komodo Edit, bajo los mismos términos que la licencia de Firefox. Esta decisión tiene sentido, pues Komodo Edit comparte parte del código fuente base de Mozilla.
Desde hace años, ActiveState desarrolla herramientas para programar en Perl, Python, PHP, Ruby y otros. También aloja varios “Cookbooks” (recetarios) en su website, por ejemplo, el imprescindible Python Cookbook.
Hay versiones para Linux, MacOs y Windows, y se puede descargar del website de ActiveState.

Filed Under: Legacy Tagged With: activestate, django, editor, Komodo, mozilla code base, Python, python cookbook

Using dynamic choices with Django newforms and custom widgets

2007-04-23 by Roberto Zoia

Most of the examples I have found on the web about replacing the default Django newforms widgets use hard coded values for the list of choices the widget displays. But those hard coded examples fall short when the widgets are bounded to many-to-many fields or to foreign keys in the model class. That is, when the list of choices is generated dynamically at run time, read from a database table.

Maybe the explanation is “over-verbose”… suggestions accepted.

Don’t hit the database twice

Consider the following model class:

# models.py Models for django blog application
from django.db import models
class Tag(models.Model):
    tag = models.CharField(maxlength=50)
class Author(models.Model):
    name = models.CharField(maxlength=100)
    email = models.EmailField()
class Article(models.Model):
        (...)
    author = models.ForeignKey(Author)
    tags = models.ManyToManyField(Tag)

The standard method for displaying a form for the model class in a webpage is to subclass forms.Form, create an instance of the subclass and return the rendered the template. Examples of this can be found on the Django website and on the web.

          #  ArticleForm is a subclass of form.Form
          ArticleForm = forms.models.form_for_model(Article)

The next step is to create an instance of ArticleForm, and return the rendered template:

        form = ArticleForm()
        return render_to_response('post.html', { 'form': form })

I don’t like lists for multiple option selection, at least not in webpages. I prefer checkboxes. It takes some CSS tweaking to render them correctly and evenly spaced on the webpage, but it greatly enhances the user experience.
The class generated by form_for_model (and by form_for_instance) stores the widgets in a variable named base_fields (a dictionary). This makes it simple to replace any of the default widgets with any other widget, provided it makes sense to do so. For example, the following code replaces the author and tags widgets with a RadioSelect widget and a CheckboxSelectMultiple widget:

        ArticleForm.base_fields['tags'].widget = CheckboxSelectMultiple(choices= ... )
        ArticleForm.base_fields['author'].widget = RadioSelect(choices= ... )

The only thing left is to provide the list of choices that both CheckboxSelectMultiple and RadioSelect constructors expect as one of their parameters. One way to build such lists is to query the database and fetch the entries for the Tag and Author classes. Something like tagChoices = Tag.objects.all() and authorChoices = Author.objects.all(). Convert the resulting queryset to a dictionary and use it as a parameter to the RadioSelect widget constructor.

        ArticleForm = forms.models.form_for_model(Article)
        ArticleForm.base_fields['tags'].widget = CheckboxSelectMultiple(
                choices=ArticleForm.base_fields['tags'].choices)
        ArticleForm.base_fields['author'].widget = RadioSelect(
                choices=ArticleForm.base_fields['author'].choices)
        form = ArticleForm()
        return render_to_response('post.html', { 'form': form })

That’s it. The Author SelectField gets replaced by radio buttons, and the Tag MultipleSelectField by some nice checkboxes. The database gets hit only once. Nothing to be ashamed of.

A complete example

Updated 2007-5-5: added editpost.html, which was not included by mistake.
The following is a test blog application I wrote. It uses form_for_instance and form_for_model to define the proper subclass of form, then replaces the standard widgets with custom ones. The widgets load the Author and Tags choices dynamically. In addition, creation and edition of the blog posts are handled by the same method. A regular expression in urls.py converts web page names to slugs as registered in the database, so permalinks are effectively implemented.

Some screenshots of the Django mini-blog

This is pretty spartan. Throw in some CSS if you like.

Django Blog
Django blog edit post

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block pagetitle %}Page title{% endblock %}</title>
    <link rel="stylesheet" href="" type="text/css" media="screen" />
       {% block head %}
       {% endblock %}
    </head>
    <body>
        {% block body %}
        {% endblock %}
    </body>
</html>
{% extends "base.html" %}
{% block title %}
    A minimal blog, powered by Django
{% endblock %}
{% block body %}
<h2>Hello, world!</h2>
<ul>
<li><a href="./add">Write new post</a></li>
</ul>
{% for post in posts %}
<div class="post">
    <h2><a href="./{{ post.slug }}.html" title="{{ post.title }}">
        {{ post.title }}</a></h2>
    <div class="postmeta">
        <div class="postdate">{{ post.pub_date }}</div>
        <div class="posttags">
        Tags:
        {% for tag in post.tags.all %}
            {{ tag.tag }},
        {% endfor %}
        </div>
        <div class="postauthor">
            By {{ post.author }}.
        </div>
    </div><!-- end postmeta -->
    <a href="./edit/{{ post.id }}">Edit post</a>
    <div class="postcontent">
    {{ post.content }}
    </div>
</div>
{% endfor %}
{% endblock %}
{% extends "base.html" %}
{% block title %}
        Add post
{% endblock %}
{% block body %}
<h2>Write a post</h2>
<form method="POST" action=".">
        {{ form.as_p }}
        <input type="submit" value="submit" />
</form>
{% endblock %}

models.py

# Models for django blog application
from django.db import models
class Tag(models.Model):
    tag = models.CharField(maxlength=50)  # tagname
    def __str__(self):
        return self.tag
    class Admin:
        pass
class Author(models.Model):
    name = models.CharField(maxlength=100)
    email = models.EmailField()
    def __str__(self):
        return self.name
    class Admin:
        pass
class Article(models.Model):
    title = models.CharField(maxlength=250)
    slug = models.CharField(maxlength=250)
    content = models.TextField()
    author = models.ForeignKey(Author)
    pub_date = models.DateTimeField('date published')
    mod_date = models.DateTimeField('date modified')
    tags = models.ManyToManyField(Tag)
    def __str__(self):
        return self.title
    class Admin:
        pass

url.py

from django.conf.urls.defaults import *
urlpatterns = patterns('',
    # Example:
    # (r'^djangotest/', include('djangotest.foo.urls')),
    (r'^blog/$', 'djangotest.blog.views.index'),
    #  regex for slugs.  Note that the regex matches even
    (r'^blog/(?P<slug>((\w+|-)*))(\.html)\/?$', 'djangotest.blog.views.single'),
    # edit post
    (r'^blog/edit/(?P<post_id>\d+)/$', 'djangotest.blog.views.edit'),
    # add new post
    (r'^blog/add/$', 'djangotest.blog.views.edit'),
    # Uncomment this for admin:
    (r'^admin/', include('django.contrib.admin.urls')),
)

views.py

# views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.template import Context, loader
from django.shortcuts import render_to_response, get_list_or_404
from django import newforms as forms
from django.newforms.widgets import *
from djangotest.blog.models import *
def index(request):
    """ Display last 5 articles """
    last_five = Article.objects.all().order_by('-pub_date')[:5]
    t = loader.get_template('blog/index.html')
    c = Context({
        'posts' : last_five,
    })
    return HttpResponse(t.render(c))
def single(request, slug):
    # handle page not found
    articles = get_list_or_404(Article, slug__exact=slug)
    return render_to_response('blog/index.html', { 'posts':articles })
def edit(request, post_id=None):
    if post_id is None:
        # no id, user is creating new post
        ArticleForm = forms.models.form_for_model(Article)
    else:
        # editing or updating post
        try:
            article = Article.objects.get(id=post_id)
        except Article.DoesNotExist:
            HttpResponseRedirect('blog/postdoesnotexist.html')
        ArticleForm = forms.models.form_for_instance(article)
    # we need to feed the new widget with the choices from the
    ArticleForm.base_fields['tags'].widget = CheckboxSelectMultiple(
        choices=ArticleForm.base_fields['tags'].choices)
    ArticleForm.base_fields['author'].widget = RadioSelect(
        choices=ArticleForm.base_fields['author'].choices)
    if request.method == 'POST':
        # form has been submited (i.e., new post or old post update)
        form = ArticleForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/blog')
    else:
        # empty form
        form = ArticleForm()
    return render_to_response('blog/editpost.html', { 'form': form })

References

    The following pages were of great help in understanding the newforms library better:

  • The Django documentation for newforms. (Still work in progress.)
  • How to use the model specified defaults as the default values for fields, and how to make the help_text entries available in the fields for rendering.

Filed Under: Legacy Tagged With: django, Python

Iterating over items of selection fields in django templates using newforms

2007-03-23 by Roberto Zoia

A year ago I wrote a custom CheckboxSelectMultiple control for django. My application needed to display a series of checkboxes on the webpage, but the default django control did not allow iteration over each checkbox when the control was rendered in the template (as it was possible with the RadioSelect control). This finer control was necessary because I needed to insert extra HTML between each checkbox.

As of version 0.95, django has been under heavy changes, and my custom control no longer works. In particular, the old forms module is being discarded in favor of the newforms module that will become the default forms module sometime in the future. A good explanation can be found in the on-site django documentation, under newforms-migration plan.

The good news is that newforms allows access to individual items of the form fields, multiple-select fields included. The newforms documentation is still work in progress, so it took me a while to figure out how to do it… by inspecting the source code and regression tests. It seems pretty obvious now, should have asked in the django-users list.

The example code has been tested with django svn release 4812 (2007-3-23).

template

<ul>
{% for choice in form.base_fields.tag.choices %}
<li> ({{ choice.0 }}, {{ choice.1 }}) </li>
{% endfor %}
</ul>

models.py

# models.py
from django.db import models
class Tag(models.Model):
    tag = models.CharField(maxlength=20)
    def __str__(self):
        return self.tag
class Post(models.Model):
    # other fields here:
    # text = models.CharField(maxlength=255)
    # title = models.CharField(maxlength=50)
    # etc.
    tag = models.ManyToManyField(Tag)

views.py

# views.py
from django.template import Context, loader
from django.http import HttpResponse, HttpResponseRedirect
from django import newforms as forms
from django.newforms.widgets import *
from project.models import *
def add_post(request):
    postForm = forms.models.form_for_model(Post)
    if request.method == 'POST':
       form = postForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/")
    else:
         form = postForm()
         t = loader.get_template('add_post.html')
         c = Context({
               'form': form,
               })
         return HttpResponse(t.render(c))

This is an old version of views.py. The code works, but as I discovered later, there is no need to create another object (p = Post(**cleandata)) to handle the many-to-many field data. form.save() takes care of everything, as expected.

# views.py
from django.template import Context, loader
from django.http import HttpResponse, HttpResponseRedirect
from django import newforms as forms
from django.newforms.widgets import *
from project.models import *
def add_post(request):
    postForm = forms.models.form_for_model(Post)
    if request.method == 'POST':
        form = postForm(request.POST)
        if form.is_valid():
           cleandata = form.clean_data
           # use the form tag ids to select the Tag instances
           # related to this Post entry
           tag = Tag.objects.in_bulk(cleandata['tag'])
           # need to delete the tag ids from clean data,
           # otherwise p = Post(** cleandata) will complain that
           # tag is not a parameter of Post( )
           del cleandata['tag']
           # create an instance of Post from the form data
           p = Post(**cleandata)
           p.save()   # need to save so p gets an id.
           p.tag = tag
           p.save()
           return HttpResponseRedirect("/")
       else:
       form = postForm()
       t = loader.get_template('add_post.html')
       c = Context({
              'form': form,
        })
        return HttpResponse(t.render(c))

Filed Under: Legacy Tagged With: checkboxes, django, newforms, Python, templates

Django and Custom CheckboxSelectMultipleField

2006-02-22 by Roberto Zoia

Note: as of Django version 0.95, this solution no longer works. But thanks to the newforms module, it is no longer necessary. Explanation of how it works can be found in this post

The problem: I have been using Django in a project for some weeks. The application needs to display a series of checkboxes on the webpage, but the HTML rendering of the formfields.CheckboxSelectMultipleField class does not offer the precise placement of each checkbox that is needed on the webpage.

formfields.CheckboxSelectMultipleField is a descendant of SelectMultipleField and does not provide the ability to iterate through each of the possible choices, as formfields.RadioSelectField derived objects do:

<!-- choices is an formfields.RadioSelectField object
      This is not possible with formfields.CheckboxSelectMultipleField derived objects.
-->
<div class="form-row">
<table><tr>
     {% for i in form.choices.field_list %}
           <td>;{{ i.field }}</td><td>{{i.name}}</td>
     {% endfor %}
     </tr>
</table>
</div>

The Solution: a custom CheckboxesMultipleSelectField based on the code from formfields.RadioSelectField.
CustomCheckboxSelectMultipleField is intended to be used in a custom Manipulator.

The code for the custom CheckboxSelectMultiple

from django.core import formfields
class CustomCheckboxSelectMultipleField(formfields.FormField):
    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None):
        self.field_name = field_name
        # choices is a list of (value, human-readable key) tuples because order matters
        self.choices, self.is_required = choices, is_required
        self.validator_list = [self.isValidChoice] + validator_list
        self.ul_class = ul_class
        if member_name != None:
            self.member_name = member_name
    def render(self, data):
        """
        Returns a special object that is iterable *and*
        has a default str() rendered output.
        This allows for flexible use in templates. You can just use the default
        rendering:
            {{ field_name }}
        ...which will output the radio buttons in an unordered list.
        Or, you can manually traverse each radio option for special layout:
            {% for option in field_name.field_list %}
                {{ option.field }} {{ option.label }}<br />
            {% endfor %}
        """
        class CustomCheckBoxSelectMultipleRenderer:
            def __init__(self, datalist, ul_class):
                self.datalist, self.ul_class = datalist, ul_class
            def __str__(self):
                "Default str() output for this radio field -- a <ul>"
                output = ['</ul><ul %s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
                output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
                output.append('</ul>')
                return ''.join(output)
            def __iter__(self):
                for d in self.datalist:
                    yield d
            def __len__(self):
                return len(self.datalist)
        datalist = []
        str_data_list = map(str, data) # normalize to string
        for i, (value, display_name) in enumerate(self.choices):
            checked_html = ''
            if str(value) in str_data_list:
                checked_html = 'checked="checked"'
            datalist.append({
                'value': value, 'checked': checked_html,
                'name': display_name,
                'field': '<input type="checkbox" id="%s" name="%s" value="%s"%s/>' % \
                    (self.get_id() + '_' + str(i), self.field_name, value, checked_html),
                'label': '<label for="%s">%s</label>' % \
                    (self.get_id() + '_' + str(i), display_name),
            })
        return CustomCheckBoxSelectMultipleRenderer(datalist, self.ul_class)
    def isValidChoice(self, data, form):
        str_data = str(data)
        str_choices = [str(item[0]) for item in self.choices]
        if str_data not in str_choices:
            raise validators.ValidationError, _("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices}

Filed Under: Legacy Tagged With: django, Python

  • What I’m Focused on Now
  • Español
  • Privacy Policy

Copyright © 2021 Roberto Zoia
zoia.org runs on WordPress using a customized version of the Parallax Pro Theme for the Genesis Framework.

This website uses cookies to improve your experience.Accept
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled

Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.

Non-necessary

Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.