Archive for the ‘Django’ Category.

Altering a Django project to migrate from a simple ManyToMany relation to one with extra information

There are still quite a few features which could be useful on Clusterify. One I thought could help in structuring projects is the concept of Roles: a way to let users joining a project specify which role they want to take in it.

Now, most common tasks are made amazingly easy with Django. Changing models, while probably common, seems very tricky to automate right, though (and anyway would you trust the automation logic to make the right changes?). Yet there’s dmigrations which I need to try one day.

Back to the original problem: Roles. Formerly we had a ManyToManyField in the Project model linked to the User model for users joining a project. In the background, Django creates a table to hold the relationship named after the field (projects_project_joined_users).

The way to add extra information to a relationship, in the Django ORM, is with the “through” argument to the ManyToManyField, which lets you specify a model which will hold the relationship. In this case, we’d get:

class Membership(models.Model):
    user = models.ForeignKey(User)
    project = models.ForeignKey(Project)
    role = models.CharField(max_length=120)
    approved = models.BooleanField(default=False)

and in Project we now add the field:

    members = models.ManyToManyField(User, through='Membership')

This would create a table named projects_membership. It still doesn’t hold any data, though. Now I see two ways of making this change in the DB:

  • Renaming or copying the old table holding the relationship, and ALTERing it until it looks like that new one.
  • Creating the new table (by looking at the output of sqlall) and filling it by copying over the data with a mapping of fields.

I chose the later, which seemed more straightforward (and anyway I have other manual copying operations to perform). There’s an easy way to copy over the data, the INSERT … SELECT syntax in SQL. The old table was named projects_project_joined_users, so the syntax becomes:

INSERT INTO projects_membership (user_id, project_id, role, approved)
    SELECT projects_project_joined_users.user_id, projects_project_joined_users.project_id, '', 1
    FROM projects_project_joined_users;

Running this copied over the old data, and now the code may be updated. Note that I had first created the new table by checking the output of “sqlall” and running the relevant statements (CREATE TABLE, ALTERs for foreign key constraints, and index creation).

It’s now more complicated to handle the relationship this way, though, as one can’t use add() or remove() as before. Take a look at the doc for more info on this.

Django and full-text search

Lately I’ve been searching for a simple solution for full-text Model search using Django. Every task up to this point just seemed so easy, so I was a bit surprised to discover there’s no quick, clean and preferred way to go about adding site search functionality in the framework.

So far, the information I read seems to suggest existing solutions are:

  • Based on a dedicated full-text search module
    • djangosearch
      • Supposed to become the official search contrib. Rather recent history (during 2008).
      • It’s an framework over existing, dedicated full text indexing engines:
    • django-sphinx
      • Wrapper around Sphinx full-text search engine
  • Based on a database engine full-text capability (ie. you must create full text indexes with appropriate DB commands)
    • For the MySQL backend, there’s already a “fieldname__search” syntax already supported in the framework, translating into a MATCH AGAINST query in SQL.
      • Supports basic boolean operators
      • Reference (look at the conclusion of the article)
    • For PostgreSQL, depending on the version of the engine, there are solutions, but they seem complex, relative to the MySQL approach
  • Most simple, but very inefficient: based on a simple LIKE %keyword% query
    • Uses the “fieldname__icontains” filter syntax
    • That’s what I used temporarily for get the feature going in my prototype

Other approaches are mentioned in this thread on StackOverflow.

Custom Django filters: displaying a date as “[time delta] ago”

I’m working on a project based on Django, these days. This framework simplifies developing web applications by a great measure, and is very flexible.

One example of this flexibility is the ability to add new functionality to template syntax. For example, I needed a way to display a date/time as a difference to the current one (“19 days ago”, “10 hours ago”).

Turns out there’s a way to do this in Django: as soon as a need can be generalized, it’s probably in there somewhere, or on djangosnippets. It’s the template filter “timesince“. Basically, in the template, you do:

{{ mydate|timesince }} ago

and it will display your date as said above. Problem is, the string is too long for my needs (it will display “19 days, 10 hours” when I need only “19 days”). So I wrapped the function in another one, in a file place under an app of my project, as explained here. Here’s the content:

from django import template
from django.utils.timesince import timesince
register = template.Library()
@register.filter(name='ago')
def ago(date):
    ago = timesince(date)
    # selects only the first part of the returned string
    return ago.split(",")[0] + " ago"

I place that under “myproject/myapp/templatetags/my_date_filter.py”. I also created an empty file __init__.py under templatetags, too. Then, in the template, I may do:

{% load my_date_filter %}
...
{% mydate|ago %}

and I get the shortened string.

Django is full of “extension points” like such. It’s a lot of fun to work with.