CurrentUserDefault doesn't work with AnonymousUser - django

I'm trying to use CurrentUserDefault with a field that can be null:
# model
class Package(models.Model):
# User can be empty because we allow anonymous donations
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
blank=True,
)
# serializer
class PackageSerializer(serializers.ModelSerializer):
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
Everything works fine when a user is logged in. However, if a user is not authenticated I get this:
ValueError at /api/organizations/village/packages/
Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x7fc97ad6d940>": "Package.owner" must be a "User" instance.
Is there a reason why CurrentUserDefault doesn't work with anonymous users?
P.S. I know I can use this instead of CurrentUserDefault and it will work:
class AuthorizedUserOrNone:
requires_context = True
def __call__(self, serializer_field):
user = serializer_field.context["request"].user
if user.is_authenticated:
return user
return None
def __repr__(self):
return "%s()" % self.__class__.__name__

What do you return if user.is_anonymous? Work that out and add that to def __call__(self, serializer_field) and you should be good. CurrentUserDefault does not appear to handle anonymous users.
Try:
get_object_or_404(User, fk_user=self.request.user)
This answer provides more helpful context: Django REST Framework - CurrentUserDefault use
Based on what I'm reading in the Traceback, unless you dive deeper into the code base behind the CurrentUserDefault class I don't think there is a standard way of handling this. My limited research has shown me that CurrentUserDefault doesn't appear to be designed to handle anonymous requests (which fits, given the word "User" in the class). The Traceback seems to align with this.
If I am correct, and you're dealing with an edge-case, then more modifications will likely be required to accomplish what you're looking for. Good luck. Sorry I couldn't help with a direct answer.
Note: the above is untested.
See: Django REST documentation

Related

Django creating a "sentinel" user on delete

I do want to keep some info about removed users (like username) to show in forum posts. How can I achieve that?
So far I have this:
class Post(models.Model):
(...)
creator = models.ForeignKey(User, blank=True, null=True,on_delete=models.SET(get_sentinel_user))
#receiver(pre_delete, sender=User, dispatch_uid='user_delete_signal')
def create_sentinel_user(sender, instance, using, **kwargs):
SentinelUser.objects.get_or_create( \
username=instance.username+" (left)")[0]
def get_sentinel_user():
return SentinelUser.objects.latest('id')
However if I use that in Admin, it doesn't work because for some reason get_sentinel_user is run sooner than pre_delete and therefor the sentinel user doesn't exist yet.
If your ultimate goal is to save data about the user, the django user docs suggest to use the is_active flag rather than deleting users. That way, you will maintain access to the user information, will not need to worry about your ForeignKey consistency, and don't have this overhead of creating a dummy user for every user that you delete.
You could then back that up by using on_delete=models.PROTECT to make sure you still protect the consistency of your database.
If for some reason you have to delete your users, I would override the delete() on Post rather than trying to use signals.
So, a sample solution could be something like:
def delete(self):
self.created = SentinelUser.objects.get_or_create(username=self.created.username+" (left)")[0]
self.save()
return super(Post,self).delete(self)

Django action (not model data directly) HTML view and API validation keeping a DRY rule

I would like to create an API for my project.
The models:
class Offer(models.Model):
user = models.ForeignKey(User)
data = models.TextField()
class Bid(models.Model):
user = models.ForeignKey()
offer = models.ForeignKey()
created_at = models.DateTimeField(auto_now_add=True)
Here's the simplified example (as there are more checks)
Both Offer & Bid users can cancel the Bid.
In my standard (HTML) view:
def cancel_bid(request, pk):
do_some checks_if request.user_is_either_Bid_or+Offer)creator()
check_if_Bid_has_been_created_for_less_than_5_minutes()
#as user can cancel his bid within 5 minutes
Now the same must be applied to Django Rest Framework (permissions or serializers).
The problem is that I need to return error messages and codes displayed in both json error response (when using api) and in my HTML views.
I have created a cancel(self, user, other kwargs) in my Bid model where check for these are performed and my custom PermissionDenied(message, code) is returned. Then in DRF and my views I simply put:
bid = Bid.objects.get(pk=pk)
try:
bid.cancel(user):
except PermissionDenied as e:
messages.error(request, e.message)
# or return HttpResponseForbidden(e.message)
in django rest framework:
class OrderCancelView(APIView):
def post(self, request, pk):
try:
order.cancel(request.user)
except PermissionDenied as e:
raise serializers.PermissionDenied(detail=e.message)
There are more actions performed in my clean() method not pointed out here.. which
makes the method very complex.
For example:
If offer expires all bids except the first one (earliest) are cancelled.. so there is no user,
as it's being made by system. I have to omit user checks then.. and so on.
What are your thoughts on this ? What is the best Django way of doing this kind of "action" validation and keeping DRY rule?
Seems like you've already made a good start, you just need to structure your cancel method on the model so that it has all of the information it needs to execute the business logic. If your logic has to be the same between DRF and the normal view code then I would extract all the business logic into the model. You might have to give it lots of arguments so that it can make all of the correct decisions, but that's better than having to repeat the logic twice.

Custom model manager for auth_user

I want to include two extra managers on the auth user model, active and inactive, to give me just active, or just inactive users. This is how the model would look (even if the it is invalid):
from django.contrib.auth.models import User
class ActiveManager(models.Manager):
def get_query_set(self):
return super(ActiveManager, self).get_query_set().filter(active=True)
class InactiveManager(models.Manager):
def get_query_set(self):
return super(InactiveManager, self).get_query_set().filter(active=False)
class User(models.Model):
# user model...
all_users = models.Manager()
objects = ActiveManager()
inactive = InactiveManager()
Where / how exactly would I place this so I can do a query such as User.inactive.all() ? Thank you.
You're going to need to use the contribute_to_class method on your Manager. Instead of the User class you have there, you will need something like this:
InactiveManager.contribute_to_class(User, 'inactive')
I suspect it doesn't matter exactly where you do this as long as it happens nice and early (before you use it!) - a models.py somewhere would feel vaguely right.

Changing self display of User in Django

I would like to see the user.email instead of the user.username when print(user) is called. This is to say that in my admin, i would like to see the emails as foreign keys.
Normally i would do in the following way as described on the django tutorial:
class Poll(models.Model):
# ...
def __unicode__(self):
return self.question
However, User class is prewritten and i don't want to mod Django. How then should i proceed?
UPDATE:
I added the following to my Model:
def email(self):
u = User.object.get(pk=self.user.id)
return u.email
How do i tie it to my list_display now?
You could define a method on your Poll class called 'get_username' or something, that returns the email address of the user instead of their actual username. Then pass 'get_username' as a parameter to your 'list_display' attribute in the ModelAdmin of your Poll class.
The use-case you define requires overriding the User.__unicode__ method.
From the django docs on list_display:
If the field is a ForeignKey, Django will display the __unicode__() of the related object.
I can't see any way around this.

Django custom managers - how do I return only objects created by the logged-in user?

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()