Automatically create new revisions of django model on save.

I have a use case I am investigating where saving an object should mark the original object as deleted, and save a new copy.

Using django, this is suprisingly easy to achieve.

Here is a simple model that does just that:

from django.db import models
class AutoSave(models.Model):
    name = models.CharField(max_length=64)
    active = models.BooleanField(default=True)
    previous = models.ForeignKey('AutoSave', null=True, blank=True,
        related_name='subsequent')

    def save(self, *args, **kwargs):
        if self.pk:
            self.previous_id = self.pk
            AutoSave.objects.filter(pk=self.pk).update(active=False)
            self.pk = None
        super(AutoSave, self).save(*args, **kwargs)

The important bits are that there is a boolean field called active, which can be used to soft-delete, as well as to indicate that this is the most recent revision of an object. There is also a reference to the previous version.

When an object is saved, we look for an existing primary key. Django uses this to determine if we are doing an INSERT or UPDATE. We want to get a reference to the current revision, then update the database version of that revision to be inactive, and then ensure the current data will create a new database record.

This is obviously a bit of a simplification, but the only way I would change it might be to see if no fields have changed, in which case we don’t need to really save a new revision.

Open in Textmate Service

Listening to the new podcast by the ever-present Dan Benjamin and the effervescent Merlin Mann today reminded me of the one Mac OS X service I wrote, that I use almost daily.

It allows me to right-click on the filename line in a python traceback, and have the file opened at that line in Textmate. If the file is part of an already open project, it will open in the project window (unless it is open in another window, in which case that may pop to front).

Fairly simple stuff, and should be easy to extend for other traceback/output types.

https://bitbucket.org/schinckel/open-in-textmate-service

django-socialregistration and 'closed' sites.

I develop a ‘closed’ system. It isn’t that not just anyone can use it - hell, we’d love to have more customers, but the users of the front-facing site (myROSS) are not the same as our customers, who use the backend indirectly through our client application.

The outcome of this is that we do not allow for registration on myROSS. The only way you can become a user of myROSS is if your company uses The ROSS System, and you have been added by your manager, franchisee or whatever.

However, I have been looking at django-socialregistration a bit lately: it is a very clean way to allow users to register and then login using Facebook, Twitter OAuth, and OpenID. Since one of the reasons that many of our ‘users’ do not use myROSS as much as we would like is because it is another password to remember, and also because most of our users are younger employees of Quick Service Restaurants, we are hoping that allowing them to use their Facebook or Twitter credentials will mean the barrier to use of myROSS is reduced.

So, I would like to be able to use socialregistration to allow currently registered users to connect their myROSS accounts to one or more of their other accounts (Twitter, Facebook, OpenID), and then allow them to use that as a means of authentication. But at the same time, I want to prevent non-current users from registering (or more to the point, prevent a current user from accidentally creating a duplicate account).

The first part is already handled by socialregistration: when a logged in user clicks on a link that connects to Twitter, for instance, and then approves the connection on the Twitter website, their account is then associated. Data is stored in the myROSS database noting which twitter account id matches which myROSS account. They can then log in using Twitter. I assume the same happens with Facebook (which I don’t use), and OpenID (which I haven’t tried yet - although I do use it for other sites).

What I need to do is prevent non-logged in users from registering.

The way socialregistration’s workflow works is that when a non-logged in user authenticates with the outside authentication system, they are redirected to a setup page, which allows them to either create a username, or generates a username for them. We need to just hook in at this time, and instead of asking for a new username/email address, ask them to enter their current myROSS credentials. At that point, we can store the information as is done if they were already authenticated.

Rather than hack the socialregistration source, we can use the hooks that the authors there have kindly left us. Specifically, we only need to override one class, the UserForm. We can do this in the view, by changing the arguments passed in to the ‘setup’ view.

If you copied-and-pasted the contents of the socialregistration urls.py file, then you can just modify the declaration of the ‘socialregistration_setup’ url route, and add in the new form class. Otherwise, you can just override that url route. I included the whole socialregistration.urls, so now the relevant section of my project urls.py looks like:

# routes for socialregistration: note the override of the first one.
(r'^auth/setup/$', 'socialregistration.views.setup', {'form_class':auth_additions.forms.ExistingUserForm}),
(r'^auth/', include('socialregistration.urls')),

There is also an import of my auth_additions.forms module.

Now, the exciting bit: what goes in the ExistingUserForm class?

from django.contrib.auth.forms import authenticate
from django.utils.translation import gettext as _
from django import forms

class ExistingUserForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
    
    def __init__(self, user, profile, *args, **kwargs):
        super(ExistingUserForm, self).__init__(*args, **kwargs)
        self.user = user
        self.profile = profile
    
    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username and password:
            self.user = authenticate(username=username, password=password)
            if self.user is None:
                raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
            elif not self.user.is_active:
                raise forms.ValidationError(_("This account is inactive."))

        return self.cleaned_data
        
    def save(self, request=None):
        self.profile.user = self.user
        self.profile.save()
        return self.user

Django templates not loading?

Had an annoying issue today after upgrading an installation of django to the trunk. All of a sudden, my admin interface would not load. I had errors stating that the template admin/login.html could not be loaded.

Now, django.contrib.admin was in settings.INSTALLED_APPS, and django.template.loaders.app_directories.Loader was in settings.TEMPLATE_LOADERS.

So, why was django throwing an exception? To find out, I stepped into a shell:

>>> from django.template.loader import find_template_loader
>>> loader = find_template_loader('django.template.loaders.app_directories.Loader')
>>> loader.load_template('admin/login.html')

This was where I realised something was wrong. I don’t get the error now (as I have fixed it), but it complained about being not allowed to open the file. As in a permissions error.

Looking up the location in a new shell, I was able to see that all of the files in the django.contrib.admin.templates directory were only able to be read by root. For some reason, python setup.py install had set the mode of these files to 0600, instead of the expected 0644. A quick sudo chmod -r ag+r templates (from inside the django.contrib.admin directory) fixed it.

Dvorak and VMWare Fusion

I made the choice a year or so ago to go Dvorak: I was on holidays and I spent the time I was free doing a typing tutor. I really liked how quickly I was able to make the transition. Most words and sentences can be written with little movement of the fingers off the home row. I also use the Programmers Dvorak layout, including swapping the orientation of the numeric keypad to be more like a phone, not a calculator. This was the only part I needed to actually swap the keycaps on my keyboards around on, as I touch-type on the main keyboard, but tend to look a bit more when entering values.

I have noticed that quite a few applications don’t handle a different keymap that well. It appears they are using the keycodes, which are independent of the values that should be presented to the operating system. I can understand why this is in many cases: for games for instance, you want to be able to continue to walk around using the aswd keys in World of Warcraft regardless of the layout of the keymap. I guess it makes a bit of sense to pass through this stuff to the virtual machine too: after all, it has its own keymap to interpret.

With VMWare Fusion, however, the translation of Mac Shortcuts to Windows Shortcuts is slightly broken. Because on my keymap, the ⌘-keys match their keycode (didn’t want to re-learn all of the shortcut keys), then they are not passed through correctly. Luckily, they are editable in the VMWare Fusion preferences. Mine now look like this:

Now, I can hit ⌘S in notepad, under Windows, and it saves my document. And I can use the cut/copy/paste/undo shortcuts again!

Am I connected remotely?

I now share my dotfiles between the various OS X machines I use daily, using Dropbox and symlinks.

However, I have many aliases and functions that need to act differently if I only have a console session at the machine in question, or a full GUI session.

With bash, this is easy to test:

export EDITOR='nano'
if [[ -z "$SSH_CONNECTION" && $OSTYPE =~ ^darwin ]]; then
export EDITOR='mate --wait'
export TEXEDIT='mate -w -l %d "%s"'
export LESSEDIT='mate -l %lm %f'
fi

Now, if I am remotely connected to a machine, then I will get nano as my editor, but if I am sitting directly in front of it, then it will open Textmate.

django-bshell

bpython is pretty cool. It gives you an improved python shell, with popups of completeable values. About the only drawback is that some command-line editing doesn’t work that well, but I can live with that.

I made a django app that provides a new management command: bshell. This will start a new shell, using bpython, and import all of your models.

You can get it with:

pip install django-bshell

And then install it into your django settings.INSTALLED apps. The app itself is called ‘bshell’. Then you can just use:

django-admin.py bshell

The code can be found on bitbucket.

Issues with PyPI/other python.org sites

I was having some issues connecting to (although not pinging) python.org and some of the python-subdomains: notably the CheeseShop (http://pypi.python.org).

I disabled IPv6, and they all cleared up.

I don’t know if this was an issue with Internode’s IPv6 stuff, but it was being handled by my Airport Extreme. My iPhone was working fine, because it doesn’t use IPv6.

What a relief.

I had tried everything I could think of: from changing my DNS server, DHCP server, I even tried a reinstall of my OS (although this wasn’t why I did that - I wanted a cleanup of my dev machine).

Django Management.tmbundle

Did some work on my Django Management.tmbundle last night.

It now handles running tests when (a) The apps are not directly in the project root, but inside another folder, for instance; and (b) the app/tests.py file has been split into seperate files.

The main reason I made this was so that I could run tests and have clickable links in the results window for the location of failing tests.

There is still much to do on this. I am considering re-writing it in python rather than ruby, so I can programmatically find the app name, rather than guess it. I also want to refactor the hell out of it and make it much nicer.

Anyway, if you are interested, you can find the most recent version at http://github.com/schinckel/Django-Management.tmbundle - and I think it also appears in TextMate’s getBundles bundle.

Images in placeholder for input fields

Since I have been doing a heap of web/html dev lately, I’ve taken to noticing things. Today, on MacWorld, I noticed this:

Google-search-placeholder

Clicking in the search box results in:

Google-search-placeholder-focus

So, there is an image in the placeholder. This is very neat!

Unfortunately, it appears this might not be a part of the HTML5 spec, but is done using JavaScript.