Waffle is a handy add-on for Django that allows fine-grain control over feature sets within an application. At VM Farms, we use Waffle to control which users have access to specific features that are under development, but deployed within our live production environment. Like I said, it’s handy!
Check out Waffle here.
Incidentally, our new portal is built on Django 1.5, and one of its new features is the ability to have fully customizable user models in django.contrib.auth. Sweet! This is great for us, as we are now able to move away from the static user model that existed within Django previously; however, despite Waffle’s best efforts to stay compatible, defining custom users can break Waffle. Boo.
Read about the issue on Github here.
Until a fix is implemented, here is a breakdown of the workaround that we used to keep us moving forward.
The Heart of The Issue
Waffle makes the following four assumptions about user models:
- user.is_authenticated() method exists
- user.is_staff property exists
- user.is_superuser property exists
- user.groups.all() returns all Group objects attached to the user - making the assumption that you are using django.contrib.auth.group and linking it to the user model as a foreign key field
This clashes with Django 1.5’s minimum requirements for a custom user model (found here):
- have an integer primary key
- have a unique field for identification purposes (a “username”)
- provide a way to address the user in ‘short’ and ‘long’ forms
Some of Waffle’s requirements are satisfied if you go through the effort of making the user model compatible with django.contrib.admin, or by having the model inherit AbstractBaseUser. However, unless your model satisfies all four of Waffle’s assumptions, it will break the code. Luckily, there are easy ways to satisfy all requirements without having to run to your database to alter tables.
It’s easy enough to fake results for is_authenticated():
Of course, if you actually plan on making use of is_authenticated() as a meaningful check, you’ll have to do more than just hard code a value to be returned. Otherwise, it’s likely that you’re not interested in checking for “authenticated users only”, and just want it to work without throwing a 500.
Faking is_staff and is_superuser Model Fields
In this example, we already make use of a is_admin boolean field to track admins, so we get it to return that value in place of is_superuser and is_staff. You can just as easily hardcode it to return True/False if the field means nothing, and you just want Waffle to work.
Faking the Groups Many-to-Many Field
When it comes to the many-to-many Foreign Key field, we don’t actually need to fake a field - we just need to somehow make the call to [usermodel].groups.all() work.
We start by defining a fake property the same way we did above for is_superuser and is_staff. However, this time we make it return a dummy object with an all() method under it:
That way, user.groups.all() is now technically a perfectly valid command, and Waffle doesn’t complain.