Having something like
created_by
created_date
modified_by
modified_date
Would be a very common pattern for a lot of tables.
1) You can set created date automatically (but not others) in model.py with
created_date = models.DateTimeField(auto_now_add=True, editable=False)
2) You could do created/modified dates (but not by/user as don't have request context) in model.py with
def save(self):
if self.id:
self.modified_date = datetime.now()
else:
self.created_date = datetime.now()
super(MyModel,self).save()
3) You could set the created/modifed date and by in admin.py - but this doesn't deal with non admin updates
def save_model(self, request, obj, form, change):
if change:
obj.modified_by = request.user
obj.modified_date = datetime.now()
else:
obj.created_by = request.user
obj.created_date = datetime.now()
obj.save()
4) And the final place would be in the view.py which can do all 4, but doesn't cover admin updates.
So realistically have to have logic spread out, at a minimum repeated in 3 & 4 (or a method on the model called from both, which will be missed)
Whats a better way? (I've been working with python/django for a couple of days so could easily be missing something obvious)
Can you do someting like #login_required e.g. #audit_changes
Can you get access to the request and current user in the model and centralise logic there?
The create/modification dates can be handled by Django now, so they can be implemented like:
class BaseModel(models.Model):
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
By adding this to a abstract model base class, it can be easily added to all models of the application.
Storing the user is harder, since the request.user is not available. As SeanOC mentioned, this is a separation of concerns between the web request, and model layer. Either you pass this field all the time, or store request.user in a threadlocal. Django CMS does this for their permission system.
class CurrentUserMiddleware(object):
def process_request(self, request):
set_current_user(getattr(request, 'user', None))
And the user tracking happens elsewhere:
from threading import local
_thread_locals = local()
def set_current_user(user):
_thread_locals.user=user
def get_current_user():
return getattr(_thread_locals, 'user', None)
For non-web environments (e.g. management commands), you'd have to call set_current_user at the start of the script.
For timestamped models you probably want to look at django-model-utils or django-extensions. They each include abstract base classes which automatically handle of a created and last modified timestamp. You can either use these tools directly or look at how they solved the problem and come up with your own solution.
As for your other questions:
Can you do someting like #login_required e.g. #audit_changes
Potentially yes but you'd have to be very careful to keep things thread-safe. What you potentially could do is in your #audit_changes decorator, set a flag to enable auditing in a threadlocal. Then either in the save method of your models or in a signal handler, you could check for your audit flag and record your audit info if the flag had been set.
Can you get access to the request and current user in the model and centralise logic there?
Yes, but you'll be making a tradeoff. As you've touched on a little bit, there is a very clear and intentional separation of concerns between Django's ORM and it's request/authentication handling bits. There are two ways ways to get information from the request (the current user) to the ORM (your model(s)). You can manually manage updating the creator/modifier information on your objects or you can set up a mechanism to automatically handle that maintenance work. If you take the manual approach (passing the information through method calls from the request in the view to the ORM), it will be more code to maintain/test but you keep the separation of concerns in place. With the manual approach, you will be in much better shape if you ever have to work with your objects outside of the request/response cycle (e.g. cron-scripts, delayed tasks, interactive shell). If you are ok with breaking down that separation of concerns, then you could setup something where you set a thread local with the current user in a middleware and then look at that thread local in the save method of your model. Converse to the manual approach, you'll have less code to deal with but you'll have a much harder time if you ever want to work with your objects outside of the request/response cycle. Additionally, you will have to be very careful to keep everything thread-safe with the more automated approach.
Can you import the User model object and call get_current()?
Also, I think you can call views in the admin.py.
Related
I have a fairly complex django application that has been in production for over a year.
The application holds data from different customers. The data is obviously in the same table, separated by customer_id.
Recently the client has started to ask questions about data segregation. Since the app is sold on a per user basis and holds sensitive information, customers have been asking if and how we maintain data segregation per customer, and are there any security measures that we take to prevent data leakages (ie. data from one customer being accessed by another customer).
We do our filters in the view endpoints, but eventually a developer in the team might forget to include a filter in his ORM query, and cause a data leakage
So we came up with the idea to implement default filters on our models. Basically whenever a developer writes:
Some_Model.objects.all()
essentially they will execute:
Some_Model.objects.filter(customer_id = request.user.customer_id)
We plan to achieve this by overriding the objects property on each model to point to a manager with a filtered queryset. Something like this:
class Allowed_Some_Model_Manager(models.Manager):
def get_queryset(self):
return super(Allowed_Some_Model_Manager, self).get_queryset().filter(
customer_id = request.user.customer_id
# the problem is that request.user is not available in models.py
)
class Some_Model(models.Model):
name = models.CharField(max_length=50)
customer = models.ForeignKey(Customer)
objects = Allowed_Some_Model_Manager()
all_objects = models.Manager() # use this if we want all objects
However our problem is that request.user is not available in models.py.
I have found several ways to solve this.
Option 1 includes passing the request.user to the manager each time. However since I am dealing with thousands of lines of old code, I don't want to go and change all of our ORM queries.
Option 2, included using threading.local() to set the request.user in the thread local data.
Something like this: https://djangosnippets.org/snippets/2179/
There is a module that seems to be doing this: https://github.com/Alir3z4/django-crequest
However, a lot of people seem to be against this idea... Namely these two discussions:
django get_current_user() middleware - strange error message which goes away if source code is "changed" , which leads to an automatic server restart
Django custom managers - how do I return only objects created by the logged-in user?
So that brings me to Option 3 which I came up with, and I can not find anybody else using it. Use the python builtins module to pass the user from the middleware to the model.
#middleware.py
import builtins
def process_request(self, request):
if request.user.id:
builtins.django_user = request.user
#models.py
import builtins
class Allowed_Some_Model_Manager(models.Manager):
def get_queryset(self):
if 'django_user' in vars(builtins):
return super(Allowed_Some_Model_Manager, self).get_queryset().filter(
customer_id = django_user.customer_id
)
else:
return super(Allowed_Some_Model_Manager, self).get_queryset()
I have tested the code and it is working on my local django server and on Apache with mod_wsgi. But I really want to hear if there are any pitfalls of this approach. I have never used builtins module before, and I am not sure if I understand how it works, and what is the use-case for it.
I'm a definite newbie to django, and have used tutorials and stack overflow a lot to design my app.
My app tracks casework for users. One thing I need it to be able to do is to be logged in as one user, and look at the caseload for another user.
When this page comes up, the bit that would normally say "Logout Joe Bloggs", where Joe Bloggs is the logged in user, says "Logout Fitzwilliam Darcy" where Fitzwilliam Darcy is the user I should be viewing the casework of.
I don't really want to continue developing the app until I've fixed it, in case this is a symptom of an underlying problem that will cause more issues in future.
I'll add what I think all the relevant bits are here, but if there's anything else needed, let me know and I'll be happy to edit.
#urls.py
url(r'cases/(?P<slug>\w+)',views.UserCasesView.as_view(),name='cases'),
#views.py
#method_decorator(login_required,name='dispatch')
class UserCasesView(generic.DetailView):
model = models.User
template_name = 'caseworkclub/caseworker_detail.html'
slug_field = 'username'
#models.py
class User(AbstractUser):
association = models.ForeignKey('Association',null=True)
def full_name(self):
return("{} {}".format(self.first_name,self.last_name)
def open_cases(self):
return(Case.objects.filter(closed__isnull=True,caseworker=self))
The slug bit is so that the username can be in the URL according to this answer to another question
It might also be worth explaining, if the models.py bit doesn't give it away, that I've extended the base user class and am using that, rather than the user profile technique. The problem seems to have come about since changing the slug bit above, though.
Any help hugely appreciated, and as I said - any more info, happy to provide!
James
This is because you did not override the get_context_data() method of your UserCasesView class with a sensible default. It currently uses the model name (which also happens to be user) which conflicts with the built-in user variable which is set by django.contrib.auth.context_processors.auth context processor.
From the documentation:
Context variables override values from template context processors
Any variables from get_context_data() take precedence over context variables from context processors. For example, if your view sets the model attribute to User, the default context object name of user would override the user variable from the django.contrib.auth.context_processors.auth() context processor. Use get_context_object_name() to avoid a clash.
Try changing the object name from user to something else (such as case_user):
class UserCasesView(generic.DetailView):
...
context_object_name = 'case_user'
I'm writing an application in Django (which I'm very new to) where the admin area will be exposed to 'customers' of the application, not just staff/superusers, because of the nature of the application and the way Django automatically generates forms in the admin area with such little code..
As such I need to robust and manageable way to maintain authentication and separating data, so only data created by a user is seen by that user.
At the moment I'm just using the default admin package and changing permissions for 'client users' to filter what data they can see (I only want them to see data they've created) using code like the below:
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(MyModelAdmin, self).get_queryset(request)
return qs.filter(user=request.user)
def save_model(self, request, obj, form, change):
# field not editable in admin area so handle it here...
obj.user = request.user
obj.save()
However as the application scales, I can see ensuring this type of data filtering becoming difficult to manage, for example if there are chains of foreign keys on certain tables(A->B B->C C->D), and to filter the table at the end of the chain I need to do various JOINs to get the rows which relate to the current user.
A couple of solutions I'm pondering are creating a separate admin app per user, but this feels like overkill and even more unmanageable.
Or just adding the user column to every Model where data filtering by user is required to make it easier to filter.
Any thoughts on the best approach to take?
First off, from experience, you're better off offering editing and creating functionality to your users in an actual django app, using Views. Generic views make this very easy. Once you let your users into the admin, they will get used to it and it's hard to get them to leave.
Additionally you should use contrib.auth.Group together with django-guardian to keep track of object-level permissions instead of implementing it yourself. It pays off in the long run.
If you want to make this experience on your own however, you have more than one sensible choice:
owner on root objects in the ForeignKey pyramid
owner on every model
To realize the first option, you should implement two methods on every model down the ForeignKey chain:
def get_parent(self):
"""Should return the object that should be queried for ownership information"""
pass
def owned_by(self, user):
"""Should return a boolean indicating whether `user` owns the object, by querying `self.get_parent().owned_by(user)`"""
pass
However, as you stated, this incurrs many JOINS if your schema is sufficiently complex.
I would advise you to store the information about the owner in every model, everything else is a maintanence nightmare in my experience.
Instead of adding the field manually to every model manually, you should use inheritance. However django provides bad built-in support for inheritance with relations: An abstract base model cannot define a models.ForeignKey, so you're stuck with table based inheritance.
Table based inheritance brings another problem with itself: Consider these models:
from django.db import models
from app.settings import AUTH_USER_MODEL
class Base(models.Model):
owner = models.ForeignKey(AUTH_USER_MODEL)
class ChildA(Base):
name = models.CharField(max_length=5)
class ChildB(Base):
location = models.CharField(max_length=5)
It is easy to find the owner of a given instance of ChildA or ChildB:
>>> obj = ChildA.objects.create(owner=Peter, name="alex")
>>> obj.owner
Peter
However it is non trivial to find all objects owned by a particular user:
>>> Base.objects.filter(owner=Peter)
<Base-object at 0xffffff>
The default manager returns a Base object, and doesn't contain information about whether it is a ChildA or ChildB instance, which can be troublesome.
To circumvent this, I recommend a polymorphic approach with django-polymorphic or django-model-utils, which is more lightweight. They both provide means to retrieve the child classes for a given Base model in the queries.
See my answer here for more information on polymorphism in django.
These also incur JOINs, but at least the complexity is manageable.
Is it possible to use Django's user authentication features with more than one profile?
Currently I have a settings.py file that has this in it:
AUTH_PROFILE_MODULE = 'auth.UserProfileA'
and a models.py file that has this in it:
from django.db import models
from django.contrib.auth.models import User
class UserProfileA(models.Model):
company = models.CharField(max_length=30)
user = models.ForeignKey(User, unique=True)
that way, if a user logs in, I can easily get the profile because the User has a get_profile() method. However, I would like to add UserProfileB. From looking around a bit, it seems that the starting point is to create a superclass to use as the AUTH_PROFILE_MODULE and have both UserProfileA and UserProfileB inherit from that superclass. The problem is, I don't think the get_profile() method returns the correct profile. It would return an instance of the superclass. I come from a java background (polymorphism) so I'm not sure exactly what I should be doing.
Thanks!
Edit:
Well I found a way to do it via something called an "inheritance hack" that I found at this site http://djangosnippets.org/snippets/1031/
It works really well, however, coming from a java background where this stuff happens automatically, I'm a little unsettled by the fact that someone had to code this up and call it a "hack" to do it in python. Is there a reason why python doesn't enable this?
So the issue you're going to have is that whatever you want for your profile, you need to persist it in a database of some sort. Basically all of the back-ends for django are relational, and thus every field in a persisted object is present in every row of the table. there are a few ways for getting what you want.
Django provides some support for inheritance. You can use the techniques listed and get reasonable results in a polymorphic way.
The most direct approach is to use multiple table inheritance. Roughly:
class UserProfile(models.Model):
# set settings.AUTH_PROFILE_MODULE to this class!
pass
class UserProfileA(UserProfile):
pass
class UserProfileB(UserProfile):
pass
To use it:
try:
profile = user.get_profile().userprofilea
# user profile is UserProfileA
except UserProfileA.DoesNotExist:
# user profile wasn't UserProfileB
pass
try:
profile = user.get_profile().userprofileb
# user profile is UserProfileB
except UserProfileB.DoesNotExist:
# user profile wasn't either a or b...
Edit: Re, your comment.
The relational model implies a number of things that seem to disagree with object oriented philosophy. For a relation to be useful, it requires that every element in the relation to have the same dimensions, so that relational queries are valid for the whole relation. Since this is known a-priori, before encountering an instance of a class stored in the relation, then the row cannot be a subclass. django's orm overcomes this impedance mismatch by storing the subclass information in a different relation (one specific to the subclass), There are other solutions, but they all obey this basic nature of the relational model.
If it helps you come to terms with this, I'd suggest looking at how persistence on a RDBMs works for applications in the absence of an ORM. In particular, relational databases are more about collections and summaries of many rows, rather than applying behaviors to data once fetched from the database.
The specific example of using the profile feature of django.contrib.auth is a rather uninteresting one, especially if the only way that model is ever used is to fetch the profile data associated with a particular django.contrib.auth.models.User instance. If there are no other queries, you don't need a django.models.Model subclass at all. You can pickle a regular python class and store it in a blob field of an otherwise featureless model.
On the other hand, if you want to do more interesting things with profiles, like search for users that live in a particular city, then it will be important for all profiles to have an index for their city property. That's got nothing to do with OOP, and everything to do with relational.
The idios app by the Pinax team aimed at solving the multiple-profile problem. You can tweak the model to make the inheritance of the base profile class either abstract or non-abstract.
https://github.com/eldarion/idios.
Here is the answer to my question of how to get multiple profiles to work:
from django.contrib.contenttypes.models import ContentType
class Contact(models.Model):
content_type = models.ForeignKey(ContentType,editable=False,null=True)
def save(self):
if(not self.content_type):
self.content_type = ContentType.objects.get_for_model(self.__class__)
self.save_base()
def as_leaf_class(self):
content_type = self.content_type
model = content_type.model_class()
if(model == Contact):
return self
return model.objects.get(id=self.id)
I don't really understand why it works or why the developers of django/python made inheritance work this way
If you have app-specific options for each user, I would rather recommend to put them into a separate model.
A simplified example:
class UserSettings(models.Model):
user = models.ForeignKey(User, primary_key = True)
# Settings go here
defaultLocale = models.CharField(max_length = 80, default = "en_US")
...
This would be used like so:
def getUserSettings(request):
try:
return UserSettings.objects.get(pk = request.user)
except UserSettings.DoesNotExist:
# Use defaults instead, that's why you should define reasonable defaults
# in the UserSettings model
return UserSettings()
I have a ForeignKey which can be null in my model to model a loose coupling between the models. It looks somewhat like that:
class Message(models.Model):
sender = models.ForeignKey(User, null=True, blank=True)
sender_name = models.CharField(max_length=255)
On save the senders name is written to the sender_name attribute. Now, I want to be able to delete the User instance referenced by the sender and leave the message in place.
Out of the box, this code always results in deleted messages as soon as I delete the User instance. So I thought a signal handler would be a good idea.
def my_signal_handler(sender, instance, **kwargs):
instance.message_set.clear()
pre_delete.connect(my_signal_handler, sender=User)
Sadly, it is by no means a solution. Somehow Django first collects what it wants to delete and then fires the pre_delete handler.
Any ideas? Where is the knot in my brain?
Django does indeed emulate SQL's ON DELETE CASCADE behaviour, and there's no out-of-the box documented way to change this. The docs where they mention this are near the end of this section: Deleting objects.
You are right that Django's collects all related model instances, then calls the pre-delete handler for each. The sender of the signal will be the model class about to be deleted, in this case Message, rather than User, which makes it hard to detect the difference between a cascade delete triggered by User and a normal delete... especially since the signal for deleting the User class comes last, since that's the last deletion :-)
You can, however, get the list of objects that Django is proposing to delete in advance of calling the User.delete() function. Each model instance has a semi-private method called _collect_sub_objects() that compiles the list of instances with foreign keys pointing to it (it compiles this list without deleting the instances). You can see how this method is called by looking at delete() in django.db.base.
If this was one of your own objects, I'd recommend overriding the delete() method on your instance to run _collect_sub_objects(), and then break the ForeignKeys before calling the super class delete. Since you're using a built-in Django object that you may find too difficult to subclass (though it is possible to substitute your own User object for django's), you may have to rely on view logic to run _collect_sub_objects and break the FKs before deletion.
Here's a quick-and-dirty example:
from django.db.models.query import CollectedObjects
u = User.objects.get(id=1)
instances_to_be_deleted = CollectedObjects()
u._collect_sub_objects(instances_to_be_deleted)
for k in instances_to_be_deleted.ordered_keys():
inst_dict = instances_to_be_deleted.data[k]
for i in inst_dict.values():
i.sender = None # You will need a more generic way for this
i.save()
u.delete()
Having just discovered the ON DELETE CASCADE behaviour myself, I see that in Django 1.3 they have made the foreign key behaviour configurable.