Django

warning: Creating default object from empty value in /var/www/drupal/modules/taxonomy/taxonomy.pages.inc on line 33.
Django is fun!

It's time to Django Bingo!

I finally got around to putting a version of Django Bingo online:

Django Bingo at Github

For an example dashboard, see my network monitor.

Note that it is very rough, I hope to improve the code over the next few
weeks. But at least you have something to play with!

Give it a try and let me know what you think (both good and bad).

Presented at the first Django Meeting NL: Django Bingo!

Held a presentation yesterday at the first ever Dutch Django meeting held at the ABC Treehouse in Amsterdam. The meeting grew out of the Python Usergroup Netherlands. The turnout was surprisingly large.

More on the meeting here, or grab my slides+notes directly.

The presentation was the first time I openly discussed this Aperte-project: Dashboards for Django (and "Bingo" was his name, o!). It features a web-based query builder and allows you to easily set up dashboards for your Django applications.

Screenshot:

I haven't released the project yet, hope to do so sometime next week. The interest in the project surprised me actually. It seems I'm not the only one who likes pretty graphs and tables, but hates all the work that surrounds building them...

EDIT: Photos of the meeting

Django admin, view-only permissions

The Django admin by default only has the add, change and delete permissions. These permissions allow you to section off your admin, allowing certain users to only modify certain objects (and with my sub-admin class you can limit permissions at an object-level!).

What the admin doesn't allow is giving read-only permissions. The reason for this, according to the Django developers, is that you must trust every user logging into the admin interface. Read-only or view permissions means you don't trust your users, thus they shouldn't have access anyway.

Of course, if we continue this reasoning, why would you have permissions at all? If you trust everyone who logs in you wouldn't need the ability to assign add, change and delete permissions. But the reason behind this is probably that the Django admin hasn't been thoroughly tested for security holes and that they'd rather not have anonymous viewing permissions, which makes sense.

I've written a quick add-view-permissions patch for Django 1.1. The results are unremarkable but effective, as the following screenshots show of a user with view-only permissions:


Feel free to use the patch, but be warned: if users logging in to your site really want to change something, they'll probably find a way through.

I've placed a couple of my Django patches together here.

For all those cheaters out there...

a nice collection of network/security cheat sheets and programming/linux cheat sheets on the web. Some useful, some pretty, some not so. And here is a pretty Django one \o/

Naturally, a real programmer like me doesn't need no stinkin' cheat sheets. Pfah! Now how do you do template time-formatting again?

Per-item, per-user permissions in Django

Although my previous post might suggest otherwise, Django 1.1 does have quite a few useful improvements. A couple of those make per-item/per-user permissions possible in the standard Django admin.

This is the case: you want to use your pretty Django admin for more than the superuser, give 'sub-admin' access. You can use permissions to reduce access to only those models a sub-administrator is allowed to use, but you can't restrict access in Django to only those items that the sub-administrator has created. For a typical multi-user blog, every sub-administrator is be able to see the blog-posts of other sub-administrators.

I've written a sub_admin class with which to restrict access for every sub-administrator to only the items he/she owns. You place it next to your admin.py file and use it in the following way.

models.py:

from django.contrib.auth.models import User
from django.db import models

class BlogPost(models.Model):
    name = models.CharField(max_length = 80)
    text = models.TextField()
    by = models.ForeignKey(User, related_name = 'blogposts', \
        null = True, blank = True, verbose_name = "Administrator")

admin.py:

from myapp.sub_admin import SubAccessAdmin
from myapp.models import BlogPost

class BlogPostAdmin(SubAccessAdmin):
    def __init__(self, *args):
        admin.ModelAdmin.__init__(self, *args)

admin.site.register(BlogPost, BlogPostAdmin)

By default SubAccessAdmin assumes you have a 'by' field in your model pointing to the User that stored the model item. SubAccessAdmin takes care of the rest: filling in the 'by' field, and restricting access for sub-administrators. Superusers naturally still have access to everything.

For more complicated models, there are a few hooks you can use (see the sub_admin source for details):

class BlogPostAdmin(SubAccessAdmin):
    def __init__(self, *args):
        admin.ModelAdmin.__init__(self, *args)
        # Restrict access to FK items of user
        self.filtered_foreignkeys = [ "foreignkey_field" ] 
        # Restrict access to M2M items of user
        self.filtered_manytomany = [ "manytomany_field" ] 

Other hooks are 'extra_excluded' (only show certain fields to superusers) and 'superuser_override_by' (allow the superuser to modify the 'by'-field).

Although it isn't perfect, it should go a long way towards allowing you to use your Django admin for users with restricted access. Improvements are welcome!

Reverting i18n/setlang behavior to Django 0.96

If there is one thing that costs a lot of time when maintaining a project, it's backwards-incompatible changes. Just think about all those Visual Basic applications (or Cobol/Fortran for those over 30) that are still being run on old environments simply because upgrading would mean a code rewrite.

Django is pretty swell in this regard. Changes are done slowly, rewrites are introduced over a number of releases and backward-incompatible changes are documented.

Even so, Django isn't perfect. Take for example the i18 set_language behavior. First you could call it directly via a GET call (ie. a simple link), which is what a normal user would expect (you click on a link to change the language), but some twit thought adhering to the HTTP RFC is more important: starting with Django 1.0 you can only call set_language via a POST call (a form submit). Of course, you could write your own GET view to get the old behavior again, but that would be superfluous.

I sniped the pre-1.0 code from the repository and changed it into a drop-in replacement (i18n.py). Simply place it in your project directory and include it:

(r'^i18n/', include('django.conf.urls.i18n')),

becomes

(r'^i18n/', include('18n')),

Naturally specifications are important, but adhering to them without thinking just leads to stupid decisions: If every page that set or modified a cookie (a change of state) would have to be called via a form you would have to rewrite 99% of the web. And it wouldn't solve anything!

Syndicate content