Our app sets the is_active field in User Model to False to represent a deleted user.
What's the best practice for excluding the deleted users (where is_active=False) from each and every access to the user table?
Please consider the following:
1. The app is already written, so we'd appreciate as minimum code changes as possible.
2. The app uses: request.user, get_object_or_404() and of course User.objects, so the solution has to take all of them into account.
From the research I've done, I found:
1. Proxy model: will force me to make a lot of changes in the code; I don't know how it works with request and get_object_or_404().
2. contribute_to_class: Can it be used to override objects manager or to just to add a new one? Is it safe?
3. Middleware changes: I don't want to get into this. Too risky for me.
Is there an elegant way for doing this?
There's no way to do this, nor should you try. The only way to limit every action to a selection of model instances is to limit the default queryset, which then effectively orphans the excluded instances, providing no way to access them ever again. The Django docs explicitly warn against this behavior.
If you override the get_query_set() method and filter out any rows, Django will return incorrect results. Don't do that. A manager that filters results in get_query_set() is not appropriate for use as an automatic manager. (emphasis mine)
"Automatic" managers are basically the same as the default manager. It's what's used for related fields, in the Django admin, and in countless other areas of the Django machinery. If you limit the default manager, you limit everything across the board.
Now, there's other options for quickly accessing the limited queryset; they simply aren't "automatic", meaning you still must make a point of using them instead of just having everything happen by magic. However, "magic" violates one of the core Python tenants in this respect: explicit is better than implicit. Limiting the User queryset by default is an implicit action. Filtering the queryset manually, referencing a custom manager method, or using a subclass of User are all explicit actions, and preferable as a result.
Related
I am using Django as back-end with graphene-django serving the front-end. I am new to both django and graphene so I am not sure what is the best approach to achieve field-level permissions with no code repetition in this set-up. For example, if my model is:
class MyModel(models.Model):
field1 = models.CharField()
field2 = models.CharField()
I want to be able to specify that user1 can read field1 but not field2; when the user1 queries GraphQL for all MyModels it would be only allow to retrieve field1 from rows (nodes) and not field2.
I did a bit of research and found 2 possible alternative solutions but I fear they may not be in the spirit of Django framework and/or conflicting with each other and there may be a better way to achieve this in Django. Ultimately I don't want to be repeating the permissions code in multiple parts of the Django and Graphene back-end so want this to be centralised at the lowest possible level, ideally at the Django model. I need to be able to control full CRUD per field depending on user_id and maybe even have some additional logic. The options I found were:
overriding get_node resolver in graphene-django DjangoObjectType to check for permissions there. Personally I see this as a very bad and last-resort solution as the checks are done only on graphql query/mutation layer and not elsewhere in Django. I could easily write a Django form or view that would not benefit from the permission check unless this is coded again in that form/view.
I could extend the Django model to perform arbitrary per-field checks and this seems the right level where to enforce permission checks but ideally I would prefer to use built-in features or a 'popular' library for this type of stuff. I tried searching for a library but I couldn't find anything that is even remotely production ready or gaining any traction - which leads me to the consider that there may be a better approach to address this problem. Django-field-permissions package seemed on the right path though.
I was wondering if anyone has a view on the best approach to solving this problem that fits Django and Graphene frameworks and without repeating the permissions code everywhere?
You're not going to find a stable/popular package for this, as it's not a design that a database can support well.
You cannot link (Foreign Key) a field and a table, you can only link two tables using a field. Therefore, any work to determine whether a row in a table has access to a field in another table, will be costly and error prone.
The best you could do, is to write a service layer that sits in between a resolver and a model that nullifies fields a user has no access to. You pass it the user (info.context.user) and a model, and it does a separate query to a field permissions model, fetches the record and nullifies each field according to permissions.
This inherently means each field needs to be nullable in order to support the permissions, complicating the frontend - it's a lot of work...
A better approach if your business logic allows it, is to group those fields into models, that can then be tied to "roles" or groups. So if this were medical data in a hospital:
- name ----- \
- address | -> Person model => all personnel, except custodial
- birth date -/
- medication -------\
- patient history | => PatientStatus model => all medically trained personnel
- current physician /
- ...
That way you use standard built-in permissions to deny access to entire tables (as illustrated above) and object level permissions (Guardian, Authority) to deny access to all but the current physician for the really classified things.
Hope this helps and good luck!
I have a system with multiple organizations logging in and interacting with us and our partners. I have a table that keeps track of what users have access to what organizations. I would like for customers to only see their own records.
I am doing this inside the views and whatnot. However, I find myself often trying to code around this. It makes it so I can't use some of the generic views as easily. Forms are a pain because when a field is pulled in as a dropdown option if shows all the records. In reality, I never want to receive all the records back. I would much rather the query check the access table and always just return what a user has access to.
I have seem some mentions about using a middleware change but I would really like to keep this within the manager and query set. It seems like that is what they are there for. However, I can't seem to find a way to reference request.user without passing it in (this causes other changes and messes with all my forms....).
Is way to do this within the manager and queryset?
I have an authentication backend based off a legacy database. When someone logs in using that database and there isn't a corresponding User record, I create one. What I'm wondering is if there is some way to alert the Django system to this fact, so that for example I can redirect the brand-new user to a different page.
The only thing I can think of is adding a flag to the users' profile record called something like is_new which is tested once and then set to False as soon as they're redirected.
Basically, I'm wondering if someone else has figured this out so I don't have to reinvent the wheel.
I found the easiest way to accomplish this is to do exactly as you've said. I had a similar requirement on one of my projects. We needed to show a "Don't forget to update your profile" message to any new member until they had visit their profile for the first time. The client wanted it quickly so we added a 'visited_profile' field to the User's profile and defaulted that to False.
We settled on this because it was super fast to implement, didn't require tinkering with the registration process, worked with existing users, and didn't require extra queries every page load (since the user and user profile is retrieved on every page already). Took us all of 10 minutes to add the field, run the South migration and put an if tag into the template.
There's two methods that I know of to determine if an object has been created:
1) When using get_or_create a tuple is returned of the form (obj, created) where created is a boolean indicating obviously enough whether the object was created or not
2) The post_save signal passes a created paramater, also a boolean, also indicating whether the object was created or not.
At the simplest level, you can use either of these two hooks to set a session var, that you can then check and redirect accordingly.
If you can get by with it, you could also directly redirect either after calling get_or_create or in the post_save signal.
You can use a file-based cache to store the users that aren't yet saved to the database. When the user logs in for the second time, you can look in the cache, find the user object, and save it to the database for good.
Here's some info on django caching: http://docs.djangoproject.com/en/dev/topics/cache/?from=olddocs
PS: don't use Memcached because it will delete all information in the situation of a computer crash or shut down.
I have a Django app that works well for me, but currently has no notion of user: I am the only one using it, and I would like to change this...
Except for the admin views, the logged-in user should not have access to the data created by other users. There is no shared data between users.
I suppose I have to add a user foreign key to all the models I created. Correct?
Is there a simple way to implement the filtering based on request.user? Can this be done more or less automatically, or do I have to go through all the code to check each and every query done on the database?
I have written the code using TDD, and I intend to follow up... What are the best strategies to ensure that user-filtering is implemented correctly, e.g. that I did not forget to filter an existing query? I suppose I can write tests that show that a particular query is not yet filtered, and implement the filter. But what about the queries that I will write later? Is there a way I can assert that all existing and future queries return objects that only belong to the current user?
Thanks.
Yes, you'll need to add a User FK. Don't forget you'll have to migrate your database tables - either manually, or via a tool like South.
One way of implementing the filter would be to define custom Managers for your models, with a for_user method that takes the User as an argument: something like:
class ForUserManager(models.Manager):
def for_user(self, user):
return self.filter(user=user)
Now you can use this manager - subclassed and/or with a mixin as necessary - on all your models, and remember to use objects.for_user(request.user) everywhere.
This will make testing easier too - your test could monkeypatch that for_user method so that it sets a flag or a counter in a global variable somewhere, and then test that it has incremented as expected.
Edit in response to comment No, as you suspect, that won't work. It's not even that everyone will necessarily get the last-logged-in user: it's that Managers are class-level attributes, and as such are reused throughout a process, so any request served by that server process will use the same one.
I am using Django admin for managing my data. I have the following tables: Users, Groups, and Domains. Users has a many-to-many relationship with both Groups and Domains. Domains has a one-to-many relationship with Groups. When I remove a User from a Domain, I also want to remove any entries in Users_Groups for that particular User and Groups belonging to the Domain.
How do I do this? Where do I put the code?
Thanks.
The Django book (specifically, Appendix B) seems to suggest you override the delete method on the User model class and have it trigger the extra deletes.
I would suggest overriding save, but I'm guessing you're using the django.contrib.auth.User object. In that case, you can accomplish about the same thing with a pre_save signal:
def manage_domains(signal,**kwargs):
if kwargs.has_key('instance'):
instance = kwargs['instance']
else: return
old_instance = User.objects.get(pk=instance.pk)
instance_categories = instance.categories.all()
for group in old_instance.groups.all():
if group not in instance_categories:
instance.groups.clear()
pre_save.connect(manage_domains, sender=User)
This is not even close to an efficient solution. What will happen is that when a User object is saved, the above changes will be made to the object in memory, then the save will overwrite the object in the database. You've gone to the trouble not only of hitting the database to find out if the unmodified version of the object agrees with with what you're about to save, but you're also looping through two category sets, both requiring a database hit to load.
The best way to make an efficiency improvement here would be to subclass the ManyToMany field manager itself, because that's what's paying attention to changes as they occur in memory, rather than merely comparing state after the fact. But this would be a bit more involved and would also require you to abandon the built-in User object.