What do I use the Serializer create method for? - django

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.

Related

Can someone explain when/how to use Queryset modification vs Permissioning with Django Rest Framework?

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.

How to pass a request.user to a model Manager?

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.

Django - Meta.base_manager_name - make related argument in the custom queryset and manager

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.

For all my foreignKeys, should i use User or UserProfile in Django?

Here is the sutuation i hit.
I have both User and ProfileUser. I would like to add additional logic to the model and since I can't add it to the Django User model, I have to add it to the ProfileUser. Currently all my models however have ForeignKey(User). Should I keep them like that or should I user ForeignKey(UserProfile) on my other models?
Example for my view if I keep the ForeignKey(User):
class myview(request):
user = request.user
userProfile = user.get_profile()
neededStuff = userProfile.get_needed_stuff()
and then in the UserProfile model:
def get_needed_stuff(self):
user= self.user # Or actually, is this right
goals = Goal.objects.get(<conditions that i wont bother writing here>)
return goals
So for this case, and for further development of the site, which foreign key should i use?
I think You should use User. UserProfile should be custom and can differ on each project. So if you will use same code for another project you can probably fail because of that. Also it is always easy to get user object in code and from that you have no problems to get profile user.get_profile() as you show (and profile is not always needed). So ingeneral I think it will be easier to use other modules and passing them just user object (or id) and not the profile.
What is also could be the solution - write your own class which will be responsible for the users. Just write methods to return profile, return stuff_needed or whatever you want and everything just by passing user object and additional parameters about what you want.
So in short, I'm for using User for Foreign keys, because in my opinion it just more logical, while the User model is always the main one (you always have it) and UserProfile is just extension.
Ignas
If you just want all the goals belonging to a specific user add a foreign key to User in your Goal model.
class Goal(models.Model):
user = models.ForeignKey(User)
def myview(request):
goals = Goal.objects.filter(user=request.user)
Or alternately save all the goals for a user on your UserProfile model and do
def myview(request):
user_profile = user.get_profile()
goals = user_profile.goals
...or use a method to do processing to calculate them
goals = user_profile.calculate_goals()
I've been pondering the same thing myself for one of my sites but i decided to use UserProfile rather than User.
Not sure if its the right decision but it just seems more flexible.

Django custom manager with RelatedManager

There must be a problem with super(InviteManager, self).get_query_set() here but I don't know what to use. When I look through the RelatedManager of a user instance,
len(Invite.objects.by_email()) == len(user.invite_set.by_email())
Even if the user does not have any invites. However, user.invite_set.all() correctly returns all of the Invite objects that are keyed to the User object.
class InviteManager(models.Manager):
"""with this we can get the honed querysets like user.invite_set.rejected"""
use_for_related_fields = True
def by_email(self):
return super(InviteManager, self).get_query_set().exclude(email='')
class Invite(models.Model):
"""an invitation from a user to an email address"""
user = models.ForeignKey('auth.User', related_name='invite_set')
email = models.TextField(blank=True)
objects = InviteManager()
'''
u.invite_set.by_email() returns everything that Invite.objects.by_email() does
u.invite_set.all() properly filters Invites and returns only those where user=u
'''
You may want a custom QuerySet that implements a by_email filter. See examples on Subclassing Django QuerySets.
class InviteQuerySet(models.query.QuerySet):
def by_email(self):
return self.exclude(email='')
class InviteManager(models.Manager):
def get_query_set(self):
model = models.get_model('invite', 'Invite')
return InviteQuerySet(model)
Try:
def by_email(self):
return super(InviteManager, self).exclude(email='')
If nothing else, the .get_query_set() is redundant. In this case, it may be returning a whole new queryset rather than refining the current one.
The documentation specifies that you should not filter the queryset using get_query_set() when you replace the default manager for related sets.
Do not filter away any results in this type of manager subclass
One reason an automatic manager is used is to access objects that are related to from some other model. In those situations, Django has to be able to see all the objects for the model it is fetching, so that anything which is referred to can be retrieved.
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.
Try using .all() in place of .get_query_set(). That seemed to do the trick for a similar problem I was having.
def by_email(self):
return super(InviteManager, self).all().exclude(email='')