Category Archives: Django

Django tip: Variable aliasing using {% with %} tags

Say you have an include that loops over a list like this…

  # render_customers.html

  {% for c in customers %}
    {{ c.first_name }}
  {% endfor %}

That is, it loops over a list called customers and only a list called customers. Now say you want to use it multiple times in your template with different lists – e.g. one called new_customers and another called old_customers – then you’d do it like this…

{% with new_customers as customers %}
  {% include "render_customers.html" %}
{% endwith %}

{% with old_customers as customers %}
  {% include "render_customers.html" %}
{% endwith %}

A simple trick but one that I don’t believe is clearly laid out in the otherwise fantastic Django documentation. Here’s what the blurb for the {% with %} tag says in the docs: “Caches a complex variable under a simpler name. This is useful when accessing an “expensive” method (e.g., one that hits the database) multiple times.”

I didn’t understand the purpose of the tag until I came across a one-line mention of it in the reusable form templates section.

Hope it helps.

Advertisements

Django tip: Showing <optgroup>s in a ModelForm

For my first Django tip on this brand new blog, I will provide some insight into my most recent head-scratcher: How do you get a ModelForm to display something like this…

…when you have your models laid out like this:

class Category(models.Model):
    name = models.CharField(max_length=50)  # Auto, Beauty, etc.

class SubCategory(models.Model):
    category = models.ForeignKey(Category)
    name = models.CharField(max_length=50)  # Parts, Fragrances, etc.

class Merchant(models.Model):
    sub_categories = models.ManyToManyField(SubCategory, verbose_name='Areas your business deals in')

On Dealit, the SubCategory model is used by many other models in a ManyToMany relationship, for example, when a merchant signs up (as I’m showing in the Merchant model above). But building out a ModelForm for Merchant will only display SubCategories without the related Category in an <optgroup>.

In order to fix that, we do this in our form…

class MerchantForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(MerchantForm, self).__init__(*args, **kwargs)
        self.fields['sub_categories'].choices = categories_as_choices()

def categories_as_choices():
    categories = []
    for category in Category.objects.all():
        new_category = []
        sub_categories = []
        for sub_category in category.subcategory_set.all():
            sub_categories.append([sub_category.id, sub_category.name])

        new_category = [category.name, sub_categories]
        categories.append(new_category)

    return categories

Here, we create a lot of nested lists with categories_as_choices() and tell Django to override the default list in the form. This is how Django wants it if you want it displaying <optgroup>s. Here’s an example from the documentation:

MEDIA_CHOICES = (
    ('Audio', (
            ('vinyl', 'Vinyl'),
            ('cd', 'CD'),
        )
    ),
    ('Video', (
            ('vhs', 'VHS Tape'),
            ('dvd', 'DVD'),
        )
    ),
    ('unknown', 'Unknown'),
)

This is exactly what we end up creating with categories_as_choices(), except we create lists instead of tuples. Both are acceptable.

Hope that helps.