I have a model manager with a get_queryset:
class BookManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author=self.request.user
This results in the error:
AttributeError: 'BookManager' object has no attribute 'request`
I understand managers are "request-unaware". How would I make a method in your manager/queryset that takes a parameter for the request.user?
Short answer: The request object is only available in a view, so you can't assume it's available in your model manager.
Pass it from your view to a method in your manager that accepts it:
class BookManager(models.Manager):
def by_author(self, user):
return self.get_queryset().filter(author=user)
Then in your view you can do Book.objects.by_author(request.user). You'd need to make sure first that request.user is a logged in user otherwise you'll get an exception.
Note: In this particular case I wouldn't go through the trouble of defining by_author since it hardly makes your code more readable. But if there are more complex queries that you often need, then it might make sense to assign them a method in your manager to create DRY code.
Related
I am struggling to understand how permissioning in DRF is meant to work. Particularly when/why should a permission be used versus when the queryset should be filtered and the difference between has_object_permission() & has_permission() and finally, where does the serializer come in.
For example, with models:
class Patient(models.Model):
user = models.OneToOneField(User, related_name='patient')
class Appointment(models.Model):
patient = models.ForeignKey(Patient, related_name='appointment')
To ensure that patients can only see/change their own appointments, you might check in a permission:
class IsRelevantPatient(BasePermission):
def has_object_permission(self, request, view, obj):
if self.request.user.patient == obj.appointment.patient:
return True
else:
return False
But, modifying the queryset also makes sense:
class AppointmentViewSet(ModelViewSet):
...
def get_queryset(self):
if self.request.user.is_authenticated:
return Appointment.objects.filter(patient=self.request.user.patient)
What's confusing me is, why have both? Filtering the queryset does the job - a GET (retrieve and list) only returns that patient's appointments and, a POST or PATCH (create or update) only works for that patient's appointments.
Aside from this seemingly redundant permission - what is the difference between has_object_permission() & has_permission(), from my research, it sounds like has_permission() is for get:list and post:create whereas has_object_permission() is for get:retrieve and patch:update. But, I feel like that is probably an oversimplification.
Lastly - where does validation in the serializer come in? For example, rather than a permission to check if the user is allowed to patch:update an object, You can effectively check permissions by overriding the update() method of the serializer and checking there.
Apologies for the rambling post but I have read the docs and a few other question threads and am at the point where I am probably just confusing myself more. Would really appreciate a clear explanation.
Thanks very much.
First, difference between has_object_permission() and has_permission() :
has_permission() tells if the user has the permission to use the view or the viewset without dealing with any object in the database
has_object_permission() tells if the user has the permission to use the view or the viewset based on a specific object in the database.
The important note thaw is that DRF wont perform the test itself in the case of object level permission, but you have to do it explicitly by calling check_object_permission() somewhere in your view (doc here).
The second important note is that DRF will not filter the result of the query based on object permission. If you want the query to be filtered, then you have to do it yourself (by overriding get_queryset() like you did or using a filter backend), that's the difference.
The serializer has nothing to do with permission neither with filtering. It handles objects one by one, applying validation (not permission) on each field of each objects.
I'd like to add a dynamic attribute to my serializers whenever they are called. Note that I'm talking about an attribute, not a field. Here's the idea:
User call the API
Viewset calls the serializer with the data
I manage to set self.my_key = my_dynamic_value
Checks and returns the serializer data (normal process)
To set my dynamic value, I need to use self.context meaning I can't override the __init__ method of the serializers (they are called on server start and context is empty)
Any idea on how I could do it?
Using another topic (Pass extra arguments to Serializer Class in Django Rest Framework), I managed to find a suitable solution.
I simply pass my dynamic value within the context by overriding the get_serializer_context method in the view, like that:
class MyViewSet(viewsets.GenericViewSet):
def get_serializer_context(self):
context = super().get_serializer_context()
context["my_key"] = my_value
return context
Then in my serializer, I can get it using self.context["my_key"]
I am trying to implement a simple flow where the a user POSTs a secret code to an api endpoint. By doing so the user creates a foreign key relationship with another model.
I achieved the desired behaviour on the serializer by overwriting the create method like so:
class RegisterUserToCustomerSerializer(serializers.Serializer):
company_code = serializers.CharField(allow_blank=False)
def create(self, validated_data):
user = validated_data['user']
try:
customer = Customer.objects.get(company_code=validated_data['company_code'])
except Customer.DoesNotExist:
return HttpResponse(status=404)
user.related_customer = customer
user.save()
return customer
In normal Django I would have implemented the behaviour on the forms save(commit=False) method. Since DRF does not have this function I feel stuck with the create() and update().
Two things really bug me about my solution:
I have to return the customer from create method eventhough the user was edited. But since the 'company_code' variable does not exist on the user DRF will through an error if I return the user
I am overwriting the create() method but I am not really creating anything. Sure, I could use update but in terms of design, this makes it even worse I fear. Everything about using the create() method feels weird about this. From accessing the user to the return statement.
Do you guys see ways to avoid this?
This doesn't feel like a job for a serializer at all. Note that you are using a plain serializer, which doesn't usually have create or update methods at all; there is no reason to create them here.
You should do this in the view.
I have a custom model manager and a custom queryset defined specifically for related obj which means I have defined Meta.base_manager_name in the model.
I would like to use a all() manager method which fetches related obj on a OneToOneFeild.
Now I know this does not make sense since OneToOneFeild will always return one obj there is no need for a all() method. I am working on django-oscar project and am extending its "Partner" model. It originally has a field "users" with ManyToManyField and now changed to a OneToOneFeild.
The users field is called in code several times using relation user.partners.all(). I don't want to extend/modify all these places (am I being lazy here?) since I want to keep the code as upgrade friendly as possible and so instead I wanted to have all() model manager defined which will work. Not sure if it is a good idea?
the all() method takes user arg to return queryset of the user instance
class PartnerQuerySet(models.QuerySet):
def all(self, user):
return self.filter(user=user)
class PartnerManager(models.Manager):
def get_queryset(self):
return PartnerQuerySet(self.model, using=self._db)
def all(self, user):
return self.get_queryset().all(users)
class Partner(models.Model):
objects = PartnerManager()
class Meta:
base_manager_name = 'objects'
The problem is when it is used with related obj it asks for user arg which makes sense but since I am using it with a related obj I wanted to use the related obj as arg so,
user.partner.all() - should use user as arg and fetch the results
user.partner.all(user) - and I should not have to do the below
2 related questions:
1) Does this make sense - should I be doing this?
2) how I can achieve user.partner.all() without adding user in arg
PS: I know i can work with middleware to get_current_user but this function is not reliable as per some of the responses on a different question on SO.
I don't think what you are trying to do will work. Your new situation with a OneToOneField gives you the partner instance.
>>>> user.partner
<Partner xxx>
While in the old situation with the ManyToManyField, the PartnerQuerySet would've been returned.
>>>> user.partner
<PartnerQuerySet []>
A solution would be to create a custom OneToOneField, but this would most probably violate the "simple is better than complex" rule and in the end may even be more work than changing all existing .all()'s.
I want to overwrite the custom objects model manager to only return objects a specific user created. Admin users should still return all objects using the objects model manager.
Now I have found an approach that could work. They propose to create your own middleware looking like this:
#### myproject/middleware/threadlocals.py
try:
from threading import local
except ImportError:
# Python 2.3 compatibility
from django.utils._threading_local import local
_thread_locals = local()
def get_current_user():
return getattr(_thread_locals, 'user', None)
class ThreadLocals(object):
"""Middleware that gets various objects from the
request object and saves them in thread local storage."""
def process_request(self, request):
_thread_locals.user = getattr(request, 'user', None)
#### end
And in the Custom manager you could call the get_current_user() method to return only objects a specific user created.
class UserContactManager(models.Manager):
def get_query_set(self):
return super(UserContactManager, self).get_query_set().filter(creator=get_current_user())
Is this a good approach to this use-case? Will this work? Or is this like "using a sledgehammer to crack a nut" ? ;-)
Just using:
Contact.objects.filter(created_by= user)
in each view doesn`t look very neat to me.
EDIT Do not use this middleware approach !!!
use the approach stated by Jack M. below
After a while of testing this approach behaved pretty strange and with this approach you mix up a global-state with a current request.
Use the approach presented below. It is really easy and no need to hack around with the middleware.
create a custom manager in your model with a function that expects the current user or any other user as an input.
#in your models.py
class HourRecordManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(created_by=user)
class HourRecord(models.Model):
#Managers
objects = HourRecordManager()
#in vour view you can call the manager like this and get returned only the objects from the currently logged-in user.
hr_set = HourRecord.objects.for_user(request.user)
See also this discussion about the middelware approach.
One way to handle this would be to create a new method instead of redefining get_query_set. Something along the lines of:
class UserContactManager(models.Manager):
def for_user(self, user):
return super(UserContactManager, self).get_query_set().filter(creator=user)
class UserContact(models.Model):
[...]
objects = UserContactManager()
This allows your view to look like this:
contacts = Contact.objects.for_user(request.user)
This should help keep your view simple, and because you would be using Django's built in features, it isn't likely to break in the future.
It seems necessary to use the middleware to store the user information.
However, I'd rather not modify the default ModelManager objects, but hook it upto a different manager, that I will use in the code, say in your case user_objects instead of objects.
Since you will use this only within views that are #login_required you dont need all the complex error handling in the Middleware.
Just my 2ยข.
Or even simpler and use foreign key to retrieve queryset.
If you have model like that
class HourRecord(models.Model):
created_by = ForeignKey(get_user_model(), related_name='hour_records')
You can query HourRecords in a view by user with simply:
request.user.hour_records.all()