Request' object has no attribute 'get': Django Views &. Serializers - python

I am working on a Dajngo DRF app serving as the API for my app.
I am really struggling to debug this error: Request object has no attribute 'get'.
The error pops up after I update my organisation. The error is being caught as an exception, so I am fairly certain it is coming from the patch method of my OrganisationDetail view. Here is the view below:
class OrganisationDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update or delete an organisation.
"""
permission_classes = (IsAuthenticated, IsInOrg)
queryset = Organisation.objects.all()
serializer_class = OrganisationSerializer
logger.info("Init log from organisation view")
def get_object(self, pk):
try:
return Organisation.objects.get(pk=pk)
except Organisation.DoesNotExist:
return HttpResponse(status=404)
def get(self, request, pk, format=None):
organisation = self.get_object(pk)
serializer = OrganisationSerializer(organisation)
return Response(serializer.data)
def patch(self, request, pk, format=None):
try:
organisation = self.get_object(pk)
data = copy.deepcopy(request.data)
member_to_remove = request.data.get(
'user_to_remove', 'no_user_to_remove')
if member_to_remove != 'no_user_to_remove':
data['member_to_remove'] = member_to_remove
if "name" not in data:
data["name"] = organisation.name
if "description" not in data:
data["description"] = organisation.description
serializer = OrganisationSerializer(
organisation, data=data, context=request, partial=True)
if serializer.is_valid():
serializer.save()
logger.info(f"PATCH SUCCESSFUL: {serializer.data}")
return Response(serializer.data)
logger.warning(f"PATCH Failed: {serializer.errors}")
return JsonResponse(serializer.errors, status=400)
except Exception as e:
logger.error(f"PATCH Failed with exception: {e}")
return JsonResponse({'error': str(e)}, status=500)
I am also going to include my serializer in the hope that this helps:
class OrganisationSerializer(serializers.Serializer):
id = serializers.ReadOnlyField()
name = serializers.CharField()
description = serializers.CharField()
owner = serializers.CharField()
members = MemberSerializer(many=True, read_only=True)
new_member = MemberSerializer(required=False)
class Meta:
model = Organisation
fields = [
'id',
'owner',
'name',
'description',
'members'
]
def create(self, validated_data):
"""
Create and return a new `Organisation` instance, given the validated data.
"""
# an organisation needs the owner to exist
# so first we create the owner as an
# inactive user and send them an activation email
user = create_user_for_organisation(
validated_data['owner'],
"ADMIN"
)
user.save(update_fields=["is_active"])
org_data = {
"owner": user,
"name": validated_data['name'],
"description": validated_data['description']
}
try:
org = Organisation.objects.create(**org_data)
org.members.set([user])
org.save()
except Exception as e:
print('Failed to create Organisation', e)
send_user_activation_email(user, self.context['request'])
send_owner_confirmation_email(user, self.context['request'])
return org
def update(self, instance, validated_data):
try:
instance.name = validated_data.get('name', instance.name)
owner = User.objects.get(
email=validated_data.get('owner', instance.owner.email))
instance.owner = owner
new_member_details = validated_data.get('new_member')
if new_member_details != None:
new_member = create_user_for_organisation(
new_member_details['email'], new_member_details['role'])
instance.members.add(new_member)
send_user_activation_email(new_member, self.context)
member_to_remove_id = QueryDict.get(
self.initial_data, key='user_to_remove')
if member_to_remove_id != None:
try:
content_lock = ContentLock.objects.get(
user_id=member_to_remove_id)
content_lock.delete()
except:
pass
user_to_remove = User.objects.get(id=member_to_remove_id)
instance.members.remove(user_to_remove)
instance.save()
return instance
except Exception as e:
logger.error(
f"Serializer Failed to update organisation with exception: {e}")
return JsonResponse({'error': str(e)}, status=500)
def validate(self, data):
try:
user = self.context['request'].user
except Exception:
user = self.context.user
except Exception:
user = self.context
if not user:
raise serializers.ValidationError("User not found.")
if not Organisation.objects.has_create_permission(user):
if not Organisation.objects.has_update_permissions(user, self.instance.owner.email):
raise serializers.ValidationError(
"User not allowed to create or update organisation.")
cleandata = data
if 'members' in self.initial_data:
if isinstance(self.initial_data['members'], list):
cleandata['members'] = self.initial_data['members']
else:
cleandata['members'] = []
cleandata['members'].append(self.initial_data["members"])
return cleandata
The error occurs after I add a user to the organisation (patch method in view, update in serializer). The user is created by updating the organisation.
There are two very strange things about this error:
The error never shows up on my localhost, and only occurs when I deploy to staging on GCP.
The user is still created, but patch request returns with a 500 response.
I unfortunately have not got my logging to propegate to GCP using stackdriver, an issue for another post. The only indication of error I can find is the one that is logged by the api client in the front end which reads: Request' object has no attribute 'get'
I am at a loss and any help is appreciated. thanks
I have tried to remove all .gets in the patch method of the view, and use some (fairly ugly) try excepts to handle unfound values in the dict. Even after removing all the dict.get() methods I still get the same error.

The answer was quite simple. When initialising the serializer to a variable, the request object needs to be passed in correctly, as demonstrated below. The request object needs to have an explicit key and the value of data should come straight from the request:
serializer = OrganisationSerializer(
organisation, data=request.data, context={'request': request}, partial=True)

Related

DRF APITestCase force_authenticate make request.user return tuple instead of User object

I have a custom authentication class following the docs
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.META.get('HTTP_X_USERNAME')
if not username:
return None
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return (user, None)
and I used it in my APIView:
class profile(APIView):
permission_classes = ()
authentication_classes = (ExampleAuthentication,)
def get(self, request, format=None):
try:
print('user', request.user)
serializer = GetUserSerializer(request.user)
return JsonResponse({'code': 200,'data': serializer.data}, status=200)
except Exception as e:
return JsonResponse({'code': 500,'data': "Server error"}, status=500)
when I try to call it normally from the API through postman I got the following result from the print and it worked normally:
user User(143)
I wrote a test using force_authenticate():
class BaseUserAPITest(APITestCase):
def setUp(self):
# self.factory = APIRequestFactory()
self.user = models.User.objects.get_or_create(
username='test_user_1',
uid='test_user_1',
defaults={'agent_type': 1}
)
def test_details(self):
url = reverse("api.profile")
self.client.force_authenticate(user=self.user)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
I got server error because the print of request.user return a tuple instead of a User object, this is the print from the test log
user (<User: User(143)>, True)
I tried searching up and seem like there no result or explanation on why this happening
My version:
django==2.2.8
djangorestframework==3.10.2
The problem is not force_authenticate but get_or_create method. It returns tuple. First element of the tuple is object and second one is boolean indicating if object was created or not. To fix change your code in setUp method to this:
def setUp(self):
# self.factory = APIRequestFactory()
self.user, _ = models.User.objects.get_or_create(
username='test_user_1',
uid='test_user_1',
defaults={'agent_type': 1}
)

Cant handle DoesNotExist error with nested resources

I'm having trouble handling DoesNotExist error, Im using DRF and DRF-Nested-Routers and when I create a new Like object I need the Photo PK so I can add it to the Like object.
I'm trying to catch the error that I get when the Photo with that PK doesn't exist.
This is how I'm doing the creation of the Like in the serializer:
class LikeModelSerializer(serializers.ModelSerializer):
""" Like model serializer. """
user = serializers.CharField(default=serializers.CurrentUserDefault())
class Meta:
""" Meta class. """
model = Like
fields = ('user', 'photo')
read_only_fields = ('user', 'photo')
def create(self, validated_data):
# Get the photo pk from the view context (DRF-nested-routers) and
# create the new like with the validated_data
photo_pk = self.context['view'].kwargs["photo_pk"]
try:
photo = Photo.objects.get(id=photo_pk)
except Photo.DoesNotExist:
return Response(data={'detail': "The photo doesn't exist."}, status=status.HTTP_404_NOT_FOUND)
validated_data["photo"] = photo
like, created = Like.objects.get_or_create(**validated_data)
if created:
photo.total_likes += 1
photo.save()
return like
The perform_create of the view:
def perform_create(self, serializer):
""" Creates a new like.
The substraction in the total_likes is made in the serializer.
"""
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(data=serializer.data, status=status.HTTP_200_OK)
The response I get with this is: {'user': 'my username here'}
I also tried with except Photo.DoesNotExist but it gives the same result.
It migth be more clear to perform the check in the validate method of the serializer class. In case of photo absence raise the serializers.ValidationError.
I have not test the code but I think that it works.
class LikeModelSerializer(serializers.ModelSerializer):
...
def validate(self, attrs):
photo_pk = self.context['view'].kwargs["photo_pk"]
try:
photo = Photo.objects.get(id=photo_pk)
except Photo.DoesNotExist:
raise serializers.ValidationError({"detail": "The photo doesn't exist"})
attrs["photo"] = photo
return attrs
def create(self, validated_data):
# Get the photo pk from the view context (DRF-nested-routers) and
# create the new like with the validated_data
like, created = Like.objects.get_or_create(**validated_data)
if created:
photo.total_likes += 1
photo.save()
return like
def perform_create(self, serializer):
""" Creates a new like.
The substraction in the total_likes is made in the serializer.
"""
if not serializer.is_valid():
raise ValidationError(serializer.errors)
serializer.save()
return Response(data=serializer.data, status=status.HTTP_200_OK)

How to provide additional data in "PUT" request for update in Django REST Framework?

Here is what I have so far:
[serializers.py]
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
user = UserSerializer(required=False)
company = serializers.CharField(read_only=True)
employee_type = serializers.ChoiceField(choices=EMPLOYEE_TYPES, default='manager')
is_blocked = serializers.BooleanField(required=False)
def update(self, instance, validated_data):
instance.user = validated_data.get('user', instance.user)
instance.company = validated_data.get('company', instance.company)
instance.employee_type = validated_data.get('employee_type', instance.employee_type)
instance.is_blocked = validated_data.get('is_blocked', instance.is_blocked)
instance.save()
return instance
[views.py]
class EmployeeDetail(APIView):
def get_employee(self, pk):
try:
return Employee.objects.get(pk=pk)
except Employee.DoesNotExist:
raise Http404
def put(self, request, pk, format=None):
employee = self.get_employee(pk)
serializer = EmployeeSerializer(employee, data=request.data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
[request]
http -a admin:password PUT http://localhost:8000/api/employees/list/2/
As a result I am able to update that employee with id=2, but the only thing that I am able to change is "employee_type" cause it has default value = 'manager', and if "employee_type" of that employee with id=2 is let's say "admin" after my request it will become "manager". The problem is that I can't figure out how to add some extra data to my request so I would be able to change "employee_type" to "director" for example, is something like below can be accomplished ?
[request_as_I_want]
http -a admin:password PUT http://localhost:8000/api/employees/list/2/employee_type='director'/company='some_value'/
Is that can be done, or I misunderstand something ?
I assume you are using httpie. To send a PUT request to django-rest-framework, a URL and json data is needed. Here's one way to do that (notice the space between URL and data):
http -a admin:password PUT http://localhost:8000/api/employees/list/2 employee_type='director' company='some_value'
See more at https://github.com/jakubroztocil/httpie#json

I am getting these error in " AttributeError: 'NoneType' object has no attribute 'get_all_permissions'

class TenantViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Tenant users to be viewed.
"""
model = TenantUser
serializer_class = TenantUserSerializer
def list(self, request, domain):
tenants = TenantUser.objects.all()
serializer = TenantUserSerializer(tenants)
return Response(serializer.data)
def retrieve(self, request, domain, pk=None):
current_tenant_user = get_current_tenant_user()
permissions = current_tenant_user.user.get_all_permissions()
if pk == 'me' or int(pk) == current_tenant_user.id:
tenant_user = current_tenant_user
else:
if not 'core.admin' in permissions:
return Response({"detail": "You don't have permission to perform this action."},
status=status.HTTP_403_FORBIDDEN)
try:
tenant_user = TenantUser.objects.get(id=pk)
except(TenantUser.DoesNotExist, Exception, ValueError):
return Response(status=status.HTTP_404_NOT_FOUND)
permissions = tenant_user.user.get_all_permissions()
serializer = TenantUserSerializer(tenant_user)
serializer.data['is_apps_admin'] = 'core.apps' in permissions
serializer.data['is_admin'] = 'core.admin' in permissions
serializer.data['is_user'] = 'core.user' in permissions
serializer.data['is_subscribed'] = 'core.active' in permissions
serializer.data['is_sales_admin'] = 'core.sales' in permissions
serializer.data['is_marketing_admin'] = 'core.marketing' in permissions
return Response(serializer.data)
My guess get_current_tenant_user function doesn't return user. Ensure that user returned from that function with return or yield statement.

TastyPie Nested POST failing to add objects - NoneType has no attribute error

I have a very simple nested model.
class Game(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
user = models.ForeignKey(User)
class Activity(models.Model):
game = models.ForeignKey(Game, related_name = 'activities')
type = models.CharField(max_length=4)
score = models.DecimalField(max_digits=3, decimal_places=2)
I can create the objects via the admin, and it works smoothly. I have then used TastyPie to create a the API. My resources look like this:
class GameResource(ModelResource):
user = fields.ToOneField(UserResource, 'user')
app = fields.ToOneField(AppResource, 'app')
activities = fields.ToManyField('api.resources.ActivityResource', 'activities', full=True, related_name='activity')
class Meta:
queryset = Game.objects.all()
resource_name = 'games'
list_allowed_methods = ['get','post']
detail_allowed_methods = ['get','put','post','delete']
authentication = ApiKeyTokenAuthentication()
authorization = Authorization()
serializer = CustomJSONSerializer(formats=['json'])
def apply_authorization_limits(self, request, object_list):
return object_list.filter(user=request.user)
def obj_create(self, bundle, request=None, **kwargs):
requestApp = App.objects.get(api_key=request.api_key)
return super(GameResource, self).obj_create(bundle, request, app=requestApp)
def get_object_list(self, request):
return super(GameResource, self).get_object_list(request).filter(app__api_key=request.api_key)
class ActivityResource(ModelResource):
game = fields.ForeignKey(GameResource, 'game')
class Meta:
queryset = Activity.objects.all()
resource_name = 'activities'
list_allowed_methods = ['get','post']
detail_allowed_methods = ['get','put','post','delete']
authentication = ApiKeyTokenAuthentication()
authorization = Authorization()
serializer = CustomJSONSerializer(formats=['json'])
def apply_authorization_limits(self, request, object_list):
return object_list.filter(game__user=request.user)
def obj_create(self, bundle, request=None, **kwargs):
return super(ActivityResource, self).obj_create(bundle, request)
def get_object_list(self, request):
return super(ActivityResource, self).get_object_list(request).filter(game__app__api_key=request.api_key)
I post from the app this dictionary with the activity nested in the game.
{
name = "Monte";
description = "To search for the holy grail.";
user = "/api/v1/users/2/";
activities = (
{
type = "eggs";
score = "0.50";
}
);
}
The result is that the Game is created in the database, but when TastyPie tries to create the nested Activity object it spits out an error. The error says that when it tries to get the list of Activities, it fails to find those that match with the api_key. I don't understand why it's trying to get the objects, and I don't understand where the NoneType came from if it's creating the Game correctly with the app that sent the request. You can see that I find the app that matches the request.api_key and set that as the app for the Game.
"error_message": "'NoneType' object has no attribute 'api_key'"
super(ActivityResource, self).get_object_list(request).filter(game__app__api_key=request.api_key)\n\nAttributeError: 'NoneType' object has no attribute 'api_key'\n"
What am I missing? Is there something wrong with my resources and the way that I'm getting the Actvities?
As a side note... I can POST a dictionary:
{
game = "/api/v1/games/2/"
type = "eggs";
score = "0.50";
}
When I post that to activities, it creates the activity correctly. And when I get the activity through the API it looks just like that.
So the problem isn't in creating the activities, it's only in creating them nested in a Game.
UPDATE:
I put some breakpoints in and found that in resource_from_data it's calling obj_update and not sending in the request, thus request = NoneType. Is this a bug in TastyPie?
def resource_from_data(self, fk_resource, data, request=None, related_obj=None, related_name=None):
"""
Given a dictionary-like structure is provided, a fresh related
resource is created using that data.
"""
# Try to hydrate the data provided.
data = dict_strip_unicode_keys(data)
fk_bundle = fk_resource.build_bundle(data=data, request=request)
if related_obj:
fk_bundle.related_obj = related_obj
fk_bundle.related_name = related_name
# We need to check to see if updates are allowed on the FK
# resource. If not, we'll just return a populated bundle instead
# of mistakenly updating something that should be read-only.
if not fk_resource.can_update():
return fk_resource.full_hydrate(fk_bundle)
try:
return fk_resource.obj_update(fk_bundle, request, **data)
except NotFound:
try:
# Attempt lookup by primary key
lookup_kwargs = dict((k, v) for k, v in data.iteritems() if getattr(fk_resource, k).unique)
if not lookup_kwargs:
raise NotFound()
return fk_resource.obj_update(fk_bundle, **lookup_kwargs)
except NotFound:
return fk_resource.full_hydrate(fk_bundle)
except MultipleObjectsReturned:
return fk_resource.full_hydrate(fk_bundle)

Categories