Re: Can anyone give me a suggestion or a recommendation as to how I can access the current user's username in the models.py?

118 views
Skip to first unread message

Jonathan D. Baker

unread,
Mar 14, 2013, 8:04:58 PM3/14/13
to django...@googlegroups.com
Perhaps a combination of signals and receivers would fit the bill? Models.py shouldn't be concerned with request objects.

Sent from my iPhone

On Mar 14, 2013, at 12:59 PM, Simon Chan <simonch...@gmail.com> wrote:

Essentially, I'm just trying to make a simple email alert system. If anyone creates, modifies or deletes an entry in the admin back end, I'll receive an email as to which user did what to which model. I already figured out how to detect the 3 user actions mentioned above in the models.py as well as how to send email templates. The issue is that I need to the current user information to insert into that email template. And like I mentioned before, this is an admin back end, meaning I'm not touching the views.py at all.

If there are any other ways to accomplish this, I'm all ears!

Thanks

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Hendrik Speidel

unread,
Mar 14, 2013, 9:17:00 PM3/14/13
to django...@googlegroups.com
Hi,

Accessing the request within models may be problematic ... E.g. when using a management command, there is no request.

However, it's doable:

-implement a middleware that takes the request or request.user and stores it in a thread local
-register this middleware in your settings
-in your model, you can now access the request/request.user by accessing the thread local variable

We have done stuff like that in some of our projects ... Works well
However, i consider it a bit of a hack...

Hope that helps

Best,
Hendrik

Sent from my mobile phone

Shawn Milochik

unread,
Mar 14, 2013, 9:24:25 PM3/14/13
to django...@googlegroups.com
I've repeatedly asked about this over the past couple of years and
there seems to be no "right" answer." This, to me, is the biggest flaw
in Django.

The official (and useless) answer is that you must pass "user" in
everywhere you save anything. This is stupid, obviously, because you
can not rewrite every third-party app to support this (among other
reasons). There was a good tutorial for using threadlocals on the
official Django wiki but someone deleted it in a fit of spite a few
years ago.

If you search for "audit user shawn milochik" in this Google Group
(https://groups.google.com/forum/?hl=en&fromgroups=#!searchin/django-users/audit$20user$20shawn$20milochik)
you'll get all the conversations I was involved in, which is, as far
as I know, all of them over the past four years.

The suggestion by Waldek Herka in one of those threads above works; I
just tested it. If anyone knows of any reason why it's a bad idea I'd
like to know.

Russell Keith-Magee

unread,
Mar 14, 2013, 11:31:56 PM3/14/13
to django...@googlegroups.com
On Fri, Mar 15, 2013 at 5:24 AM, Shawn Milochik <sh...@milochik.com> wrote:
I've repeatedly asked about this over the past couple of years and
there seems to be no "right" answer." This, to me, is the biggest flaw
in Django.

There's definitely a right answer. 
 
The official (and useless) answer is that you must pass "user" in
everywhere you save anything. This is stupid, obviously, because you
can not rewrite every third-party app to support this (among other
reasons).

Can someone please explain to my why it is "stupid" that a function should accept as an argument everything that it needs to perform it's job? This is basic algorithm design, not black magic.

I'm utterly amazed that for some reason, when it comes to web programming, global variables have somehow become acceptable. Because that's what a thread local is - a global variable. 

I can't speak for anyone else, but my first year university curriculum pointed out why global variables shouldn't be used. 20-odd years of software development experience has reinforced this understanding. 

Django itself uses thread locals in a couple of internal components, and let me tell you from experience -- every single one of them causes a world of hurt when you start trying to debug edge cases, or when you're trying to implement tests of functionality. If my time machine was working, I'd be jumping back in time to purge every last use of thread locals from Django's codebase. 
 
There was a good tutorial for using threadlocals on the
official Django wiki but someone deleted it in a fit of spite a few
years ago.

No - there was *a* tutorial several years ago, and it was deleted because members of the core team got tired of jumping into every conversation where it was referenced to chime in, yet again, that it was bad practice, with a whole bunch of potential security implications.

Fixing these problems the right way requires some planning. But for me, "it's harder to do this properly" isn't a compelling reason to bugger up your system architecture.

If a third party app doesn't provide a way to pass in the user object, then *fix the damn third party app*. This is open source, after all, and an inability to pass in relevant parameters to an operation is a bug (or at least a missing feature).

And if there's somewhere that Django doesn't do a good job about making it easy to pass around relevant parameters, *suggest an improvement*. I can think of a couple of places in the forms library where there is obvious room for improvement (possible to work around at present, but not as elegant as a baked-in solution).

Yours,
Russ Magee %-)

Mike Dewhirst

unread,
Mar 15, 2013, 3:58:26 AM3/15/13
to django...@googlegroups.com
On 15/03/2013 10:31am, Russell Keith-Magee wrote:
>
>
> On Fri, Mar 15, 2013 at 5:24 AM, Shawn Milochik <sh...@milochik.com
> <mailto:sh...@milochik.com>> wrote:
>
> I've repeatedly asked about this over the past couple of years and
> there seems to be no "right" answer." This, to me, is the biggest flaw
> in Django.
>
>
> There's definitely a right answer.

Russell

What is the right way to design a system whereby on every save for every
model the updated_by column is changed to the user.id of the logged-in
user? This has to happen whether updates are done via views or the
Django-Admin.

And I'm saying anonymous users can't update at all.

I actually got this working some months ago using Marty Alchin's
CurrentUserField from his Pro Django. It was a bit complex for my level
of competence and I took it out as part of a simplification exercise
when I was debugging something diabolical and didn't put it back for
another reason but that's another story.

Thanks

Mike

Russell Keith-Magee

unread,
Mar 15, 2013, 5:56:28 AM3/15/13
to django...@googlegroups.com
On Fri, Mar 15, 2013 at 11:58 AM, Mike Dewhirst <mi...@dewhirst.com.au> wrote:
On 15/03/2013 10:31am, Russell Keith-Magee wrote:


On Fri, Mar 15, 2013 at 5:24 AM, Shawn Milochik <sh...@milochik.com
<mailto:sh...@milochik.com>> wrote:

    I've repeatedly asked about this over the past couple of years and
    there seems to be no "right" answer." This, to me, is the biggest flaw
    in Django.


There's definitely a right answer.

Russell

What is the right way to design a system whereby on every save for every model the updated_by column is changed to the user.id of the logged-in user? This has to happen whether updates are done via views or the Django-Admin.

And I'm saying anonymous users can't update at all.

The brief version?

class MyModel(Model):
   ...
   def save(self, user, *args, **kwargs):
       self.updated_by = user
       return super(MyModel, self).save(*args, **kwargs)
 
From there, you just need to follow that path back to the place where the model is saved by the form. All the admin save handlers have access to the request, from which you can extract the user; form construction in the admin is also abstracted behind an interface, so you can modify the arguments passed to the form at time of construction; and forms can be modified to pass down the user data as needed to the call to save().

Now - I'm not for a second claiming that this is something that Django does well -- but it *can* be done (I know, because I've done it). 

And, more to the point, I'd look very sympathetically on any proposal or patch that made it easier to do this sort of thing -- a lot more sympathetically than I would on saying "This is hard, lets go shopping and use lots of global variables".

Yours,
Russ Magee %-)

Mike Dewhirst

unread,
Mar 15, 2013, 8:35:00 AM3/15/13
to django...@googlegroups.com
On 15/03/2013 4:56pm, Russell Keith-Magee wrote:
>
>
> On Fri, Mar 15, 2013 at 11:58 AM, Mike Dewhirst
> <mi...@dewhirst.com.au <mailto:mi...@dewhirst.com.au>> wrote:
>

<snip>

>
> What is the right way to design a system whereby on every save for
> every model the updated_by column is changed to the user.id
> <http://user.id> of the logged-in user? This has to happen whether
> updates are done via views or the Django-Admin.
>
> And I'm saying anonymous users can't update at all.
>
> The brief version?
>
> class MyModel(Model):
> ...
> def save(self, user, *args, **kwargs):
> self.updated_by = user
> return super(MyModel, self).save(*args, **kwargs)
>
> From there, you just need to follow that path back to the place where
> the model is saved by the form.

> All the admin save handlers have access to the request, from which
> you can extract the user;

> form construction in the admin is also abstracted behind an
> interface, so you can modify the arguments passed to the form at
> time of construction;

> and forms can be modified to pass down the user data as needed to
> the call to save().

Thanks Russell. I understand that (after a bit of lip-chewing and
serious frowning) and will tackle it in due course. Ain't open source nice?

Cheers

Mike


>
> Now - I'm not for a second claiming that this is something that
> Django does well -- but it *can* be done (I know, because I've done
> it).
>
> And, more to the point, I'd look very sympathetically on any proposal
> or patch that made it easier to do this sort of thing -- a lot more
> sympathetically than I would on saying "This is hard, lets go
> shopping and use lots of global variables".
>
> Yours, Russ Magee %-)
>

malt...@gmail.com

unread,
Dec 12, 2014, 10:29:35 AM12/12/14
to django...@googlegroups.com
Thanks Mike and Russell, this is very helpful for starters. Do you have some more verbose code examples I can use as crutches while I hobble along the path of understanding Django? Especially an expansion on something like "All the admin save handlers" would be much appreciated.

For me every change needs to be tracked, not just ones from the admin realm, and the audit trail entry is written by a trigger function which gets the current user name from a variable set for the postgres connection. My quest so far was to find the magical place where I have access to the request (for the username) and the db.connection (for setting the database variable), which – if I understand correctly – does not exist. So right now I was about writing my own middleware class with a process_view. Would that be the right place and how would I introduce the username into the data flow?


Sincerely,

Malte

Collin Anderson

unread,
Dec 14, 2014, 7:13:20 PM12/14/14
to django...@googlegroups.com
Hi,

The "admin save handlers" refers to save_model() and there's actually a nice example of accessing the user.

If you're using the admin, that's a good place to record this sort of thing.

Using a middleware is also helpful for a more general approach, though be careful because connections can now be re-used over multiple requests. Also, if you're deploying using threading, be sure what you are doing is thread-safe.

Collin

Fred Stluka

unread,
Dec 15, 2014, 1:15:14 AM12/15/14
to django...@googlegroups.com
Collin and Russell (and anyone else),

Do you have any opinion on this?
- https://bitbucket.org/aptivate/django-current-user

It was offered in an earlier post:
- https://groups.google.com/d/msg/django-users/y7aIbN2_CsA/GtmrSjG1nq8J

as a solution to exactly this problem.

Makes the current user available to the save() method of all
models.

I read the code and it looks good, but I don't know the details
of Apache/WSGI/Django/Python multi-threading well enough
to know if it is thread safe. 

It basically uses django.db.models. signals.pre_save.connect()
to get a callback called just before each save(), and that callback
was generated by a middleware layer to know what the current
user was for a request, so it sets a model field with a name like
update_user to the current user just before the save().

If there were 2 concurrent HTTP requests, would this work
reliably?  Or would there just be 2 registered callbacks, that
would overwrite each other's values in the update_user field
before the save()?  If so, one user would be recorded for saves
done by both requests.

It seems to me that this would work fine if each HTTP request
was handled by a separate process, but not if handled by
separate threads that share the same memory, and presumably
the same signals and callbacks.

Thoughts?
--Fred
Fred Stluka -- mailto:fr...@bristle.com -- http://bristle.com/~fred/
Bristle Software, Inc -- http://bristle.com -- Glad to be of service!
Open Source: Without walls and fences, we need no Windows or Gates.
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.

Russell Keith-Magee

unread,
Dec 15, 2014, 1:37:11 AM12/15/14
to Django Users
On Mon, Dec 15, 2014 at 9:14 AM, Fred Stluka <fr...@bristle.com> wrote:
Collin and Russell (and anyone else),

Do you have any opinion on this?
- https://bitbucket.org/aptivate/django-current-user

It was offered in an earlier post:
- https://groups.google.com/d/msg/django-users/y7aIbN2_CsA/GtmrSjG1nq8J

as a solution to exactly this problem.

Makes the current user available to the save() method of all
models.

I read the code and it looks good, but I don't know the details
of Apache/WSGI/Django/Python multi-threading well enough
to know if it is thread safe. 

I haven't deeply audited the code, but based on the approach, I can't think of any reason it wouldn't be. 

A signal is just a mechanism for registering pieces of code to run at known points in object lifecycle (in this case, just before the internal mechanics of save() are executed). There's nothing here that should trigger any multithreaded or cross-process concerns AFAICT. These signals aren't fired in a separate thread - it's completely linear code execution, via a code path that has been injected via signal registration.


Thoughts?

IMHO: It's much better than a thread local, but my own engineering taste says I'd still prefer an explicit call.

I think the current-user package is a fine example of the possibilities afforded by signals. The problem is that I don't like signals. I've been burned by signal based frameworks in the past - if you lean on signals as an engineering approach, it's *really* easy to get into an exponential signal explosion (i.e., one signal causes 2 signals to be emitted, which causes the first signal to be emitted again, and so on). Signals are sometimes unavoidable, but as a matter of taste, I avoid them if at all possible.

I vastly prefer an explicit argument, for two reasons.

1) It's very clear that it's being used, and where the value for the argument is coming from. If you use the signal approach, and I'm reading your code, and I don't know that you're using the CurrentUser signal approach, then it will be completely opaque to me why and how the current user is being specified - and there's no immediately obvious source of places to start digging. I have to audit the entire codebase to find the signal that is doing the job. This *could* be addressed with good project on-boarding documentation... but when was the last time you got given a complete and correct on-boarding document when joining an existing project :-)

2) The signal approach won't (necessarily) work in all situations - an explicit argument will. For example, if you want something on a form to be pre-populated by the current user, there isn't a signal waiting to be used. Best case, you'll have to define your own "pre-form-render" signal - which is certainly possible, but it isn't something that is waiting to be used out of the box, and you'll need to modify the Middleware to honour that signal as well. This means your middleware will need to have a signal for every possible way that a current user might be used, and signals aren't free - a signal with no listeners still has a processing overhead. Essentially, this means that Current-User *can't* be both high-performance, *and* applicable to every situation. You either need to customise it for every project where you use it, or wear a non-trivial overhead for supporting signals you may never use.

Yours,
Russ Magee %-)

Collin Anderson

unread,
Dec 16, 2014, 3:45:36 AM12/16/14
to django...@googlegroups.com, fr...@bristle.com, FredS...@gmail.com
Hi Fred,

I don't see how it could possibly be thread-safe without using a thread-local. But, if you don't deploy using threads then you should be fine.

I agree with Russ that explicit is better than implicit.

I think there is some benefit to simply being able to ask for the the current request object wherever you need it, though I've been able to avoid it pretty much every time. The main downside is that it simply doesn't work in the shell or any outside-request scripts. I imagine it's also harder to test.

There are libraries out there to do the thread-local technique for you, but it's pretty much this simple:

import threading
store
= threading.local()
class Middleware:
   
def process_request(self, request):
        store
.request = request
   
def process_response(self, request, response):
       
del store.request
       
return response

Then you can access store.request wherever you want.

Or, for fun, you can do far more evil with less code :)

import inspect
for line in inspect.stack():
   
if 'request' in line[0].f_locals:
        request
= line[0].f_locals['request']

Collin
Bristle Software, Inc -- http://bristle.com -- Glad to be of service!
Open Source: Without walls and fences, we need no Windows or Gates.
On 12/14/14 2:13 PM, Collin Anderson wrote:
Reply all
Reply to author
Forward
0 new messages