I'm building a Django app that will work against a pre-existing PostgreSQL database. Every user data table in my DB has an auditing column called last_updated_by. The requirement I need to meet is that every time an UPDATE is run against a table, that column is automatically set to the id of the logged-in user.
By "automatically", I mean that this is done without any effort (or choice) on the part of the Django developer who writes an UPDATE (either as a SQL query or as an update on a Django object representing a row of that table) on a particular user data table.
For example (from https://docs.djangoproject.com/en/1.7/topics/db/queries/), the declarer of the Blog class, which corresponds to the blog table, can declare the class as:
class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField()
and a developer can write an update as:
>>> b5.name = 'New name' >>> b5.save()
Neither the declarer or the developer gave a thought to the last_updated_by column. But when logged-in user 12345 causes this code to execute, the blog.last_updated_by column of that row will be set to 12345.
The nub of this problem is that when the database receives an UPDATE query, that query does not identify the Django end user that caused the query to be submitted. The DB contains a list of the users (auth_user), and my DB also contains a list of the currently active user sessions (and the logged-in user of each session). But multiple users can be logged in at once, and there is no way for the DB to deduce which of them issued this particular update. The fact that this update came from user 12345 is known only within the Django app.
So the problem is: How to get this info from the app to the database, without requiring the developer to put this bit of housekeeping explicitly into every update?
Three possibilities have occurred to me:
1. A decade ago, when I was using the Zope framework, I found the Zope module (called a database connector) that actually formulates the SQL query strings en route from Zope apps to the DB. I wrote a routine that inserts "last_updated_by = <the Zope function that returns the user id>" into the SET clause of each UPDATE query, and called this routine from the Zope database connector code. This worked. But it's an ugly hack and, worse, involves tinkering with the framework's DNA, so it would not have survived the next version of the database connector module.
2. Could this be done by defining last_updated_by in an abstract base class, as in https://docs.djangoproject.com/en/1.7/topics/db/models/, that includes an update method that automatically assigns the user id to last_update_by for each update?
3. Or could it be done with a modified Manager, with a modified QuerySet (https://docs.djangoproject.com/en/1.7/topics/db/managers/)?
I'm a relative newbie to Django, so I'm trying to get clear that I'm understanding you.
Assuming that my understanding isn't too far off, let me say that I'm looking for a solution that is "universal" and "magic". (And I'm willing to live with the risks of "magic"). I think this requires a solution at a deeper level of middleware ("deeper" meaning closer to the DB and farther from app code), some level that all DB update actions go through.
Let me float two more not-even-half-baked ideas for your comments:
~ Tx, Ken
I don't see any way this is feasible, if you want it to cover raw SQL
executed through ``cursor.execute``. Are you planning to parse the SQL
for every raw statement,
figure out if its an INSERT or UPDATE,
and then
figure out which row(s) it might affect?
I think this is probably technically possible (though I don't see why
you'd drop the database user when they logout, better to keep the
account around for their next login). I also expect it will be quite a
tough slog, take weeks or more likely months to get working reliably,
and require you to become an expert on the internals of the Django ORM.
I also think it's the only feasible path to your "universal and magic" goal.
(Here's a blog post where someone tried this and apparently got it
working, though I see at least three things they did along the way that
are terrible hacks and either insecure or quite likely to break:
http://blog.everythingtastesbetterwithchilli.com/2010/02/07/per-user-database-authentication-in-django-/)
Ken: Here are the aspects of this scheme that I'm more and less confident about:
Ken: I'd appreciate comments and suggestions about any and all parts of this approach.
Ken: If it is feasible, this does satisfy my criteria of being "universal" (all DB updates thru the Django app, whether thru object model save() or raw SQL or whatever, would appropriately set last_updated_by) and "magic" (once the login process is programmed to do step 4, last_updated_by would be properly set without any action of the developer who programs each update action).
Ken: Thanks again!~ Tx again, Ken