Staff are not Patrons

I have been working with the Django framework since 2012, and see some trends that have a foundation more in dogma than utility. One such trend is subclassing the AbstractBaseUser model and getting Django to recognise it thru the AUTH_USER_MODEL setting. The reason for doing this is mostly because authentication and authorization are scary. When faced with taking on this responsibility, it is tempting to turn to the default, in some cases only to find out that the client did not want any authentication for the patrons to the site. The trouble is that the standard User model has a username field which can be anything. There is a misconception that if you want this to be an email address, and get all the built in field validation, you need to to change the field called username into a field called email and make it a models.EmailField. You will then need some user_type field to determine the user as staff or a visitor to the site. Then comes the need for complex permissions and other processing to make sure that visitors don't have access to pages and functionality only to be accessed by staff, and any accidental functionality (such as Django CMS top menu) does not appear when a patron and not a staff member is logged in. I put forward the argument that there is a better way.

The Widely Accepted Implementation of User

As an example, this code is a snippet of a web application to provide curriculum material to students. Though it uses the EmailBaseUser, it is essentially the same as subclassing the AbstractBaseUser. Three types of user visit the site.
  1. Staff who update the content by logging into the Content Management System (CMS)
  2. Teachers who can edit some content via some interfaces, but not the CMS or Django admin backend
  3. Students who can see the student excersizes
Standard way to implement a user model where you have staff, teachers, students and parents
Each user type is assigned a role. In this particular application, it was required that the teachers and students not use passwords to log in to the site, but the staff needed to authenticate, as they had access to the content management system, so a default password was set for all new users, then any users that had to be staff would have their role changed, and the password set.
The first problem was the backend CMS. It is/was Django CMS, and it had no mechanism to differentiate users according to role. Any authenticated user was expected to be someone with some access to the CMS, so the tool bar that was associated with every page appeared at the top when teachers and students were logged in, as well as staff. Complex template logic was then employed to make the Django CMS menu hidden when a patron and not a staff member was logged in, and this was very buggy and often caused the bar to show for staff on some pages but not others. Some staff were also teachers, and the inevitable confusion was hard to explain or circumvent.

The second problem was when it came time to update Django and Django CMS. The User model is fundamental to Django and Django CMS. Both modules are separate but intrinsically entwined within both is the User model. When you come to update Django or Django CMS in this scenario, it wont work. Something will break. You fix it, then something fundamental breaks. You try to fix it and fail. You set exception checking to bypass errors. You try to turn authentication off entirely so the thing can be used, and then you realise that the thing can't be updated, and try your best to keep things running for as long as you can on an operating system and applications that are 15 years old, until TLS 1.0 is depreciated and the latest webserver running on the antiquated operating system can't run TLS 2.0 which is now required by all modern browsers.

A Better Way

The django.contrib.auth.models.User model is great for staff. Each staff member has their own username, but there is nothing stopping this being an email address, and using an email address can be enforced using forms. The username field itself does not have to be a models.EmailField. CharField is fine with form validation.
What we need for Patrons is a dedicated model. In this example, the patrons are Students and Teachers and we use a model for both that takes a name and email address, links them to their school, and links them to the excersizes that they have done.
The Patron Model
But, the standard Django user model provides nice decorators for view functions so a staff member has to be logged in to use the view function (@staff_member_required). How can we get the same functionality for Patrons?

Step 1: Login

Django can store serializable information on the session, and though we can not store a complex structure like a patron, we can store an integer patron ID. That is exactly what the Patron model's login function does (see code section above). There is also an instance function that determines if the Patron instance is logged in.

Step 2: Having a request.user

Django view functions are passed the request, and Django conveniently makes the logged in user available via the request.user directive. We can do this with the Patron too with the magic of middleware. Middleware is code that is called after the request is made but before a response hits Django routing. In middleware you can access the request and your models. In my project I have an application called institutions where I define Patrons (either Teachers or Students) and the Schools that they belong to. I create a new file in the institutions application called middleware.py.
Middleware
I wont explain SimpleLazyObject here, suffice to say that I copied an repurposed the Django middleware for putting the user on the request (django contrib auth middleware AuthenticationMiddleware) so that my own Patron instance was also accessible from the request inside Django view functions and classes. In the settings, you can direct Django to execute your middle ware by putting it in the MIDDLEWARE array. These get executed in order of occurrence I believe, and I have the Patron middleware running last.
Middleware Settings

Step 3: Authorization

Now to create the decorator that allows a view function to be executed only if a Patron is logged in, and in this case, that the Patron is a teacher.
patron_required
And the function that determines that the patron is a teacher is here.
User Passes Test
Once again, this was copied and repurposed from Django source, in this case django contrib admin views decorators staff_member_required. Once I have this, I can write a mixin for my class based views by making it the leftmost parameter of any class based views, and these views will require an authenticated Teacher Patron to be logged in to be executed. If a Teacher Patron is not logged in, the request will be redirected to the login page.
Patron Required Mixin

Python and Keeping Things Simple

Python has a paradigm of keeping things simple and of having things explicit. Django has a cookie cut structure when you use the django-admin tools to create a project and its applications. There is a trend to discard this structure, and part of that trend I associate with subclassing the AbstractUserModel. Almost every project I have come across does this as a matter of convention, and it breaks things, makes updating Django painful if not impossible without creating a new project, and copy pasting the code from the old project, and then using SQL to massage the database to work with what is essentially a rewritten project. Staff are not Patrons, and they deserve completely separate models.