Modify DRF Request objects for dispatch to other views - python

Context
AWS Elastic Beanstalk Worker tiers can be configured to accept a single AWS SQS queue automatically. Incoming messages on this queue can be routed to a single endpoint which some instance in the worker tier will respond to; they show up to the workers as POST events.
The software my workers are running is based on Django/DRF.
Goal
I want my worker tier instances to be able to handle more than one type of incoming event. Ideally, SQS could be configured to deliver the requests to more than one endpoint, but that appears to be impossible.
My implementation
class DispatchSerializer(serializers.Serializer):
"`to` is the name of the destination endpoint; `payload` is the data I want delivered"
to = serializers.CharField(
max_length=80,
)
payload = serializers.JSONField()
#api_view(http_method_names=['POST'])
#permission_classes([AllowAny])
def dispatch(request):
"""
Dispatch a request to a sibling endpoint.
"""
routing_table = {
... # this is specific to my application;
# it's just a dict of names to views
}
serializer = DispatchSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
to = serializer.validated_data['to']
try:
view = routing_table[to]
except KeyError:
raise Http404()
# https://github.com/encode/django-rest-framework/blob/master/rest_framework/request.py#L183-L187
request._full_data = serializer.validated_data['payload']
return view(request)
# If this returns other than 200, SQS simply re-attempts the request later.
# No returned data is preserved, so we might as well not return any.
return Response()
As you can see, I attempt to simply replace _full_data attribute of the request with the payload, so that the inner view sees only the data intended for it (which to the dispatch view is the payload).
The problem
This implementation doesn't actually work. I get errors of this form:
Traceback (most recent call last):
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/core/handlers/exception.py", line 39, in inner
response = get_response(request)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 483, in dispatch
response = self.handle_exception(exc)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 443, in handle_exception
self.raise_uncaught_exception(exc)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 480, in dispatch
response = handler(request, *args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/decorators.py", line 52, in handler
return func(*args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/worker/views.py", line 196, in dispatch
return view(request)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 483, in dispatch
response = self.handle_exception(exc)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 443, in handle_exception
self.raise_uncaught_exception(exc)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 480, in dispatch
response = handler(request, *args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/decorators.py", line 52, in handler
return func(*args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/core/views/mailchimp.py", line 229, in wrapper
return func(*args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/worker/views.py", line 52, in wrapper
result = f(log_entry, *args, **kwargs)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/worker/views.py", line 106, in match_one
user_id = int(request.data['user_id'])
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
return super(Request, self).__getattribute__(attr)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 186, in data
self._load_data_and_files()
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 246, in _load_data_and_files
self._data, self._files = self._parse()
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 290, in _parse
stream = self.stream
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
return super(Request, self).__getattribute__(attr)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 173, in stream
self._load_stream()
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 270, in _load_stream
self._stream = six.BytesIO(self.body)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 382, in __getattribute__
return getattr(self._request, attr)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 382, in __getattribute__
return getattr(self._request, attr)
File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/http/request.py", line 264, in body
raise RawPostDataException("You cannot access body after reading from request's data stream")
django.http.request.RawPostDataException: You cannot access body after reading from request's data stream
Problem restatement
The request object is complicated; generating a new one is implausible. At the same time, we need to modify the data of this one in order to properly pass it along to the next view, so that view only sees the data in its expected format.
How can I accomplish this?
[edit] Passing an un-modified request doesn't work either
I tried editing the above example to remove any modification of the request object, and instead created the following serializer mixin:
class UndispatcherMixin:
"""
Adjust a serializer such that it can accept its normal serialization,
or alternately the payload of a DispatchSerializer.
"""
def is_valid(self, raise_exception=False):
if not hasattr(self, '_validated_data'):
assert hasattr(self, 'initial_data'), (
'Cannot call `.is_valid()` as no `data=` keyword argument was '
'passed when instantiating the serializer instance.'
)
ds = DispatchSerializer(data=self.initial_data)
if ds.is_valid(raise_exception=False):
self.initial_data = ds.validated_data['payload']
return super().is_valid(raise_exception=raise_exception)
This mixin was then added to the relevant serializers used by the downstream views. Interestingly, it failed with the same django.http.RawPostDataException as before.
It's received wisdom all over SO that you can just call your views as normal functions, so long as you pass in a proper request object. This may not actually be true, at least for the case where the view was created by the DRF #api_view decorator.
Still working on a way to actually solve this.

For whatever reason--this may be an error in DRF, but I'm not sure--you can't just call an #api_view function from within another one and pass along the request object. It just doesn't work.
What does work is to factor out the code which does the actual work, and call it separately from the #api_view function and also the dispatch view. That is, something like this:
def perform_action(request, data):
"""
Do something
`request` is included for completeness in case it's necessary,
but the data normally read from `request.data` should be read instead
from the `data` argument.
"""
serializer = ActionSerializer(data=data)
if serializer.is_valid():
... # Whatever your action should be, goes here
#api_view(http_method_names=['POST'])
#permission_classes([AllowAny])
def action(request)
perform_action(request, request.data)
return Response() # 200, no content
#api_view(http_method_names=['POST'])
#permission_classes([AllowAny])
def dispatch(request):
"""
Dispatch a request to a sibling endpoint.
"""
routing_table = {
'action': perform_action, # etc...
}
serializer = DispatchSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
to = serializer.validated_data['to']
try:
view = routing_table[to]
except KeyError:
raise Http404()
view(request, serializer.validated_data['payload'])
return Response()

Related

Django / Django Rest Framework ModelViewSet: __init__() takes 1 positional argument but 2 were given

I'm trying to create a custom "list" under OptionViewSet but It's giving me an error.
class OptionViewSet(viewsets.ModelViewSet):
serializer_class = OptionSerializer
queryset = Option.objects.all()
def list(self, request):
queryset = Option.objects.all()
serializer = OptionSerializer(queryset, many=True)
return Response(serializer.data)
Error
__init__() takes 1 positional argument but 2 were given
This works fine:
class OptionViewSet(viewsets.ModelViewSet):
serializer_class = OptionSerializer
queryset = Option.objects.all()
urls.py
path('api/shipping/', include((shipping_routes.urls, 'shipping'), namespace='shipping')),
routes.py
routes.register(r'option', OptionViewSet, basename='option')
serializer
class OptionSerializer(serializers.ModelSerializer):
class Meta:
model = Option
fields = ['id', 'extra', 'name']
full traceback
Traceback (most recent call last):
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\viewsets.py", line 125, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
raise exc
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "C:\Users\Simon\Documents\GitHub\crud-react-django\django_backend\shipping\views.py", line 18, in list
return Response(serializer.data)
Exception Type: TypeError at /api/shipping/option/
Exception Value: __init__() takes 1 positional argument but 2 were given
I had the wrong Response import. Imported it from "requests" instead of "rest_framework.response".
In the url.py, in the the specific path from urlpatters[] you need to call your views.OptionViewSet.as_view() as a middle parameter between the URL path and the name reference:
path('api/shipping/', include((shipping_routes.urls, 'shipping'), namespace='shipping')),
Django has very good documentation and you can read more about the Django-Rest-Framework's ViewSets here:
https://www.django-rest-framework.org/api-guide/viewsets/
edit: From the exception stack trace we can see that url.py is refering to a problem with /api/shipping/option/ which essentially is the routes.py's record with basename='option'
Seems like you might need to adjust routes.py from
routes.register(r'option', OptionViewSet, basename='option')
to
routes.register(r'option', OptionViewSet.as_view(), basename='option')
Can you please give it a go and let us know how it goes!

Test of Django ProfileListView fails with ValueError: Cannot assign "<SimpleLazyObject:....>": "Profile.user" must be a "User" instance

I am a Django beginner and a SOF newbie, sorry if this question sounds a silly to some.
I am struggling with my integration tests.
In my app I have a one-to-one User/Profile relationship. I have a list view to show registered users' profile data:
class ProfileListView(views.ListView, LoginRequiredMixin):
model = Profile
template_name = 'registration/profile_list.html'
paginate_by = 8
def get_context_data(self, *, object_list=None, **kwargs):
context = super(ProfileListView, self).get_context_data()
# superuser raises DoesNotExist at /accounts/profiles/ as they are created with createsuperuser in manage.py
# hence are not assigned a profile automatically => create profile for them here
try:
context['current_profile'] = Profile.objects.get(pk=self.request.user.pk)
except ObjectDoesNotExist:
Profile.objects.create(user=self.request.user)
context['current_profile'] = Profile.objects.get(pk=self.request.user.pk)
# get all other users' profiles apart from staff and current user
regular_users = User.objects \
.filter(is_superuser=False, is_staff=False, is_active=True) \
.exclude(pk=self.request.user.pk)
context['non_staff_active_profiles'] = Profile.objects.filter(user__in=regular_users)
return context
I want to test the get_context_data() method to ensure it returns:
correct logged in user
correct queryset of non-staff profiles
My test breaks as soon as I try something like:
response = self.client.get('/accounts/profiles/')
I understand I need to pass user/profile data to the client but I could not figure out how to do that. It looks like it fails because of context['current_profile'] = Profile.objects.get(pk=self.request.user.pk) and I have no idea why.
The whole 'test' is below:
def test_view_get_context_data__should_return_correct_context(self):
new_user = User.objects.create_user(**self.VALID_USER_DATA_1)
# create profile
new_profile = Profile.objects.create(user=new_user)
# test profile
self.assertEqual(new_profile.user_id, new_user.pk)
response = self.client.get('/accounts/profiles/')
It fails with:
/home/kk/Documents/Github/Phonotheque/venv/bin/python /snap/pycharm-professional/280/plugins/python/helpers/pycharm/django_test_manage.py test Phonotheque.accounts_app.tests.views.test_ProfileListView.ProfilesListViewTests.test_view_get_context_data__should_return_correct_context /home/kk/Documents/Github/Phonotheque
Testing started at 15:54 ...
Found 1 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/views/generic/list.py:91: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'Phonotheque.accounts_app.models.Profile'> QuerySet.
return self.paginator_class(
Destroying test database for alias 'default'...
Error
Traceback (most recent call last):
File "/home/kk/Documents/Github/Phonotheque/Phonotheque/accounts_app/views.py", line 138, in get_context_data
context['current_profile'] = Profile.objects.get(pk=self.request.user.pk)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/query.py", line 496, in get
raise self.model.DoesNotExist(
Phonotheque.accounts_app.models.Profile.DoesNotExist: Profile matching query does not exist.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/kk/Documents/Github/Phonotheque/Phonotheque/accounts_app/tests/views/test_ProfileListView.py", line 65, in test_view_get_context_data__should_return_correct_context
response = self.client.get('/accounts/profiles/')
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/test/client.py", line 836, in get
response = super().get(path, data=data, secure=secure, **extra)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/test/client.py", line 424, in get
return self.generic(
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/test/client.py", line 541, in generic
return self.request(**r)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/test/client.py", line 810, in request
self.check_exception(response)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/test/client.py", line 663, in check_exception
raise exc_value
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 84, in view
return self.dispatch(request, *args, **kwargs)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 119, in dispatch
return handler(request, *args, **kwargs)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/views/generic/list.py", line 174, in get
context = self.get_context_data()
File "/home/kk/Documents/Github/Phonotheque/Phonotheque/accounts_app/views.py", line 140, in get_context_data
Profile.objects.create(user=self.request.user)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/query.py", line 512, in create
obj = self.model(**kwargs)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/base.py", line 541, in __init__
_setattr(self, field.name, rel_obj)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 338, in __set__
super().__set__(instance, value)
File "/home/kk/Documents/Github/Phonotheque/venv/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 235, in __set__
raise ValueError(
ValueError: Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x7f682f11e220>>": "Profile.user" must be a "User" instance.
Process finished with exit code 1
How do I simulate creation of multiple users/profiles, logging in of one of them and obtaining the relevant data?
Thanks a lot in advance.
It was quite a specific question and I very much doubt anyone ever will be looking at this answer but just in case:
def test_view_get_context_data__with__logged_in_user_should_return_correct_context(self):
user_data = {'username': 'BayHuy', 'password': '11111111', }
new_user = User.objects.create_user(**user_data)
new_profile = Profile.objects.create(user=new_user)
self.assertEqual(len(User.objects.all()), 1)
self.assertEqual(new_profile.user_id, new_user.pk)
self.assertEqual(len(Profile.objects.all()), 1)
self.client.login(**user_data)
response = self.client.get(reverse('profiles-list'))
self.assertEqual(
new_profile,
response.context_data['current_profile'])
self.assertEqual(
new_profile, response.context_data['current_profile'])
self.assertEqual(len(response.context_data['profile_list']), 1)

How solve the problem with clean function in django

I'm building a booking website, where customer can book an appointment on a day of his/her choice and the time of appointment is selected from the available slots with the help of drop-down menu. The problem is if a time slot has already been booked by someone it should not be available and an error message should be shown to the customer.
I have written a clean function to perform the check. It gives and error as follows :-
No booking on 2019-06-08
Internal Server Error: /
Traceback (most recent call last):
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/views/generic/base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/views/generic/base.py", line 97, in dispatch
return handler(request, *args, **kwargs)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/views/generic/edit.py", line 172, in post
return super().post(request, *args, **kwargs)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/views/generic/edit.py", line 141, in post
if form.is_valid():
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/forms/forms.py", line 185, in is_valid
return self.is_bound and not self.errors
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/forms/forms.py", line 180, in errors
self.full_clean()
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/forms/forms.py", line 383, in full_clean
self._post_clean()
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/forms/models.py", line 403, in _post_clean
self.instance.full_clean(exclude=exclude, validate_unique=False)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/db/models/base.py", line 1181, in full_clean
self.clean_fields(exclude=exclude)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/db/models/base.py", line 1223, in clean_fields
setattr(self, f.attname, f.clean(raw_value, self))
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/db/models/fields/__init__.py", line 629, in clean
value = self.to_python(value)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/db/models/fields/__init__.py", line 2187, in to_python
parsed = parse_time(value)
File "/home/gireesh/PycharmProjects/astrobookinenv/lib/python3.6/site-packages/django/utils/dateparse.py", line 89, in parse_time
match = time_re.match(value)
TypeError: expected string or bytes-like object
The Code for the clean function :-
def clean_booking_time(self):
booked_time = self.cleaned_data['booking_time']
booked_date = self.cleaned_data['booking_date']
# count = Booking.objects.filter(booking_date=booked_date).filter(booking_time=booked_time).count()
count_date = Booking.objects.filter(booking_date=booked_date).count()
if count_date == 0:
print("No booking on ",booked_date)
return self.cleaned_data
else:
count_time = Booking.objects.filter(booking_date=booked_date).filter(booking_time=booked_time).count()
if count_time != 0:
error_message = "%s is not available !" % booked_time
raise ValidationError(error_message)
else:
return self.cleaned_data
If I book an appointment by removing the clean it'll save successfully and then if I add the code back and try to book on the same time the code works fine and throws the error. It only seem not to work when the date has no booking. Problem might be with the ORM I have written.
You're returning the cleaned_data dict in a field-specific clean method. You are supposed to return just the value for that field.
However it seems that this should actually be the overall clean() method, since it references multiple fields. That method is meant to return the whole dict.
Just rename the method to clean.
You should return the booking_time data for the clean_booking_time function:
def clean_booking_time(self):
booked_time = self.cleaned_data['booking_time']
booked_date = self.cleaned_data['booking_date']
booking_exists = Booking.objects.filter(
booking_date=booked_date,
booking_time=booked_time
).exists()
if booking_exists:
error_message = "%s is not available !" % booked_time
raise ValidationError(error_message)
return booked_time
It furthermore is a bit odd that you clean a specific field here, since your cleaning constrains the relation between two fields.
That being said, you might want to consider a DateTimeField [Django-doc] that contains both the time and the date.

Getting "Exception: You cannot access body after reading from request's data stream" while working with DRF and django-revproxy

I'm using django-revproxy and Django REST Framework in my project. And I'm exposing an API where users get analytics about their chatbots, and it works as the following:
The user send requests the analytics from the Django project
Django project, checks if the user is authenticated and owns that chatbot
if True it contacts another external service.
My urls.py:
# urls.py
urlpatterns = (
url(r'^analytics/(?P<path>.*)$', api.AnalyticsFunctionsProxyView.as_view()),
)
And in my view:
# views.py
from rest_framework.authentication import TokenAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from revproxy.views import ProxyView
from .permissions import HasChatBotPermission
...
class AnalyticsFunctionsProxyView(ProxyView):
upstream = settings.ANALYTICS_FAAS_URL
def parse_body(self, request):
if isinstance(request, rest_framework.request.Request):
return request.data
return super(AnalyticsFunctionsProxyView, self).parse_body(request)
#classmethod
def as_view(cls, *args, **kwargs):
view = super(AnalyticsFunctionsProxyView, cls).as_view(*args, **kwargs)
view = permission_classes((IsAuthenticated, HasChatBotPermission,))(view)
view = authentication_classes([TokenAuthentication, JSONWebTokenAuthentication])(view)
view = api_view(['GET', 'POST'])(view)
return view
And my HasChatBotPermission permissions
#permissions.py
class HasChatBotPermission(permissions.BasePermission):
def has_permission(self, request, view):
try:
bot_name = request.data.get('name')
user = request.user
self.message = 'Permission denied for {}'.format(name)
return ChatBot.objects.filter(user=user, project_name=project_id).exists()
except Exception:
self.message = 'Permission denied, no project_id was defined!'
return False
When the view is called it raises this exception:
Traceback (most recent call last):
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/request.py", line 379, in __getattribute__
return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/exception.py", line 42, in inner
response = get_response(request)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/fcmam5/anaconda3/lib/python3.6/contextlib.py", line 52, in inner
return func(*args, **kwds)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 477, in dispatch
response = self.handle_exception(exc)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 437, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 474, in dispatch
response = handler(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/decorators.py", line 52, in handler
return func(*args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/revproxy/views.py", line 204, in dispatch
proxy_response = self._created_proxy_response(request, path)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/revproxy/views.py", line 139, in _created_proxy_response
request_payload = request.body
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/request.py", line 383, in __getattribute__
return getattr(self._request, attr)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/http/request.py", line 264, in body
raise RawPostDataException("You cannot access body after reading from request's data stream")
django.http.request.RawPostDataException: You cannot access body after reading from request's data stream
The issue is caused by this line bot_name = request.data.get('name') in my permissions.py, when I pass a string directly it passes without any problems.
My question is:
How can I access the request body without having this error? Why I'm having this error?
Is there a better solution for checking the user permission with Django revproxy.
This is my first question in Stackoverflow, sorry if my question is not clea, and for my poor English :)
You're having this error because django-revproxy attempts to read the raw request body so it can create a proxy request to the upstream server.
However, with Django's (and WSGI's, and buffering) semantics, this isn't possible once you've accessed the request body as anything but a raw stream, which you do when you request.data.get('name'). This parses the request body as JSON, HTTP multipart, whatever, depending on DRF's request negotiation configuration, and consumes the stream.
There are two ways you could get around this, as far as I see:
pass bot_name somewhere else than the body; a query string parameter, an HTTP header, part of the URL, for instance, so you don't need to access the body, or
make the backend request yourself using requests instead of reverse-proxying (which is basically the same thing, but with added magic to try and copy the request through as-is).

View function mapping is overwriting an existing endpoint function when using a decorator

I am using the decorator below to authenticate endpoints in my application.
from google.appengine.api import users
from flask import redirect, render_template, request
from google.appengine.ext import ndb
def authenticate_admin(func):
def authenticate_and_call(*args, **kwargs):
user = users.get_current_user()
if user is None:
return redirect(users.create_login_url(request.url))
else:
email = user.email()
register_user_if_required(email, user)
if not users.is_current_user_admin():
return redirect_to_unauthorized(email)
return func(*args, **kwargs)
def redirect_to_unauthorized(email):
return render_template('xxxx/vvvv.html',
email=email,
users=users)
return authenticate_and_call
def register_user_if_required(email, user):
I have the following endpoint which only allows administrators to access it.
#admin_routes.route('/xxxx')
#authenticate_admin
def xxxxx():
return render_template('xxxx/xxxxx.html',
user=user,
logout=users.create_logout_url('/'))
And it works in the sense that only administrator's can access the above endpoint. However when I try to add a new endpoint with the same annotation but a different fancy url I get an error. Here is the code for the endpoint.
#admin_routes.route('/xxxx/bbbbbb')
#authenticate_admin
def abc():
.....
return render_template('xxxx/xxxx/zzzzz.html',
user=user,
breadcrumb=breadcrumb)
And here is the error I get when I run my app.
Traceback (most recent call last):
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 85, in LoadObject
obj = __import__(path[0])
File "/Users/vinay/App-Engine/xxxxx/main.py", line 61, in <module>
app.register_blueprint(admin_routes)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
return f(self, *args, **kwargs)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 889, in register_blueprint
blueprint.register(self, options, first_registration)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 153, in register
deferred(state)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 172, in <lambda>
s.add_url_rule(rule, endpoint, view_func, **options))
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 76, in add_url_rule
view_func, defaults=defaults, **options)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
return f(self, *args, **kwargs)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 984, in add_url_rule
'existing endpoint function: %s' % endpoint)
AssertionError: View function mapping is overwriting an existing endpoint function: admin_routes.authenticate_and_call
Traceback (most recent call last):
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 240, in emit
record.exc_info)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2520, in RunInTransactionCustomRetries
return RunInTransactionOptions(options, function, *args, **kwargs)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2630, in RunInTransactionOptions
ok, result = _DoOneTry(function, args, kwargs)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2650, in _DoOneTry
result = function(*args, **kwargs)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 270, in __EmitTx
handler=self.__RelativePath(os.environ['PATH_TRANSLATED']))
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/request_environment.py", line 113, in __getitem__
return self._request.environ[key]
KeyError: 'PATH_TRANSLATED'
Logged from file wsgi.py, line 263
INFO 2015-08-09 03:19:14,731 module.py:812] default: "GET / HTTP/1.1" 500 -
You need to ensure your decorator wrapper has the same name as the wrapped view function, otherwise all your views look like the same endpoint (authenticate_and_call).
You can do so with the #functool.wraps() utility:
from functools import wraps
def authenticate_admin(func):
#wraps(func)
def authenticate_and_call(*args, **kwargs):
user = users.get_current_user()
if user is None:
return redirect(users.create_login_url(request.url))
else:
email = user.email()
register_user_if_required(email, user)
if not users.is_current_user_admin():
return redirect_to_unauthorized(email)
return func(*args, **kwargs)
def redirect_to_unauthorized(email):
return render_template('Admin/UnauthorizedAdmin.html',
email=email,
users=users)
return authenticate_and_call
This ensures that metadata such as the function name is copied over from func to the authenticate_and_call wrapper. From there on out #Flask.route() can pick up that name to use as the endpoint name.

Categories