Modifying oscarapi to show some endpoints as public from admin endpoints - django

What I am trying to achieve is to customize the oscarapi to expose the partner api to be public api instead of just for admin
I have followed the docs on how to customize the api and also did as suggested by Jamie Marshall in
Extending django-oscarapi API ROOT to custom API class
So far I am able to overwrite the root.py file but failing to get oscar see the new urls.py file.
My work so far is as follows
I created a api_customization/views/partner.py file
I created a api_customization/views/root.py file
I tried to extend the urls.py file by creating a api_customization/urls.py file
However, I'm getting the following error
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.7/contextlib.py", line 74, in inner return func(*args, **kwds)
File "/usr/local/lib/python3.7/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 70, in view return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 509, in dispatch response = self.handle_exception(exc)
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 469, in handle_exception self.raise_uncaught_exception(exc)
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception raise exc
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 506, in dispatch response = handler(request, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/rest_framework/decorators.py", line 50, in handler return func(*args, **kwargs)
File "/code/.../forked_apps/api_customization/views/root.py", line 52, in api_root apis = PUBLIC_APIS(request, format)
File "/code/.../forked_apps/api_customization/views/root.py", line 29, in PUBLIC_APIS ("partners", reverse("partner-list", request=r, format=f)),
File "/usr/local/lib/python3.7/site-packages/rest_framework/reverse.py", line 47, in reverse url = _reverse(viewname, args, kwargs, request, format, **extra)
File "/usr/local/lib/python3.7/site-packages/rest_framework/reverse.py", line 60, in _reverse url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 87, in reverse return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
File "/usr/local/lib/python3.7/site-packages/django/urls/resolvers.py", line 685, in _reverse_with_prefix raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'partner-list' not found. 'partner-list' is not a valid view function or pattern name.
[09/Jul/2021 21:18:13] "GET /api/ HTTP/1.1" 500 136185
views/partner.py
from oscarapi.utils.loading import get_api_class
from oscar.core.loading import get_model
from rest_framework import generics
PartnerSerializer = get_api_class("serializers.product", "PartnerSerializer")
Partner = get_model("partner", "Partner")
class PublicPartnerList(generics.ListCreateAPIView):
queryset = Partner.objects.all()
serializer_class = PartnerSerializer
urls.py
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from oscarapi.utils.loading import get_api_class
from oscarapi import urls
PublicPartnerList = get_api_class("views.partner", "PublicPartnerList")
urls.urlpatterns += [
path("partners1/", PublicPartnerList.as_view(), name="partner-list"),
]
urls.urlpatterns += format_suffix_patterns(urls.urlpatterns)
views/root.py
import collections
from django.conf import settings
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
__all__ = ("api_root",)
def PUBLIC_APIS(r, f):
return [
# other urls .......
("partners", reverse("partner-list", request=r, format=f)),
]
# remaining file content ......
I need a direction or a hint on how to achieve this
Any help is appreciated

I found a solution to this and might be helpful form someone
I had to do the same steps done for root.py file to get my app recognise the custom urls.py file
So what I did
copy the content of urls.py
modify the file to suit my needs
update my app urls.py file to point to the custom urls.py file of the api
//So from this
path("api/", include("oscarapi.urls")),
//To this
path("api/", include("APP_NAME.forked_apps.api_customization.urls")),

Related

DRF spectacular not discovering custom auth extension classes

when extending a new Token Authentication class from rest_framework_simplejwt.authentication.JWTAuthentication drf-spectacular swagger-ui authorize button disappears and there is no way to add token bearer, I guess when you subclass it goes wrong.
steps to reproduce:
first, create a Django project with rest framework and drf-spectacular and simple jwt installed and configured with documentation guidance. got to /swagger-ui/ and it works fine.
then create a subclass of JWTAuthentication like below:
from rest_framework_simplejwt.authentication import JWTAuthentication as JWTA
class JWTAuthentication(JWTA):
pass
and in your settings:
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': (
'path_to_your_module.JWTAuthentication',
),
}
and now if you go to /swagger-ui/ there is no authorize button!!! how can I fix this?
and I even tried to create an AuthenticationExtension like:
from drf_spectacular.contrib.rest_framework_simplejwt import SimpleJWTScheme
class SimpleJWTTokenUserScheme(SimpleJWTScheme):
target_class = 'path_to_your_module.JWTAuthentication'
but there is no way to register it anywhere nor on the internet nor in the documentation!!
how can I fix authorize button when overriding an Authentication class??
Edit: doing what JPG says and importing extension in settings:
# settings.py
from path.to.custom.extension import SimpleJWTTokenUserScheme
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': (
'path_to_your_module.JWTAuthentication',
),
}
raises exception:
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/drf_spectacular/views.py", line 67, in get
return self._get_schema_response(request)
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/drf_spectacular/views.py", line 74, in _get_schema_response
return Response(generator.get_schema(request=request, public=self.serve_public))
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/drf_spectacular/generators.py", line 250, in get_schema
paths=self.parse(request, public),
File "/home/hamex/current/spec/env/lib/python3.8/site-packages/drf_spectacular/generators.py", line 218, in parse
assert isinstance(view.schema, AutoSchema), (
AssertionError: Incompatible AutoSchema used on View <class 'drf_spectacular.views.SpectacularAPIView'>. Is DRF's DEFAULT_SCHEMA_CLASS pointing to "drf_spectacular.openapi.AutoSchema" or any other drf-spectacular compatible AutoSchema?
Update-1
from the doc Where should I put my extensions? / my extensions are not detected
The extensions register themselves automatically. Just be sure that
the python interpreter sees them at least once. To that end, we
suggest creating a PROJECT/schema.py file and importing it in your
PROJECT/__init__.py (same directory as settings.py and urls.py)
with import PROJECT.schema. Please do not import the file in
settings.py as this may potentially lead to cyclic import issues.
Original Answer
It seems a bug with the package itself. You can use the actual class instead of path to class while extending the auth extension
from drf_spectacular.contrib.rest_framework_simplejwt import SimpleJWTScheme
from path.to.custom.jwt.auth import JWTAuthentication
class SimpleJWTTokenUserScheme(SimpleJWTScheme):
target_class = JWTAuthentication
I have created a simple example here, drf-spectacular-example, hope someone will benefit from it!!!

Django RestFramework JWT Token: Get User DoesNotExist error

After a user is deleted, the tokens on the client side are still valid until the time has expired. The issue is django restframwework does not handle a request from a deleted user and causes a 500. How can I prevent this?
aceback (most recent call last):
File "/lib/python3.6/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/lib/python3.6/site-packages/django/core/handlers/base.py", line 179, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/lib/python3.6/site-packages/django/views/generic/base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "/lib/python3.6/site-packages/rest_framework/views.py", line 505, in dispatch
response = self.handle_exception(exc)
File "/lib/python3.6/site-packages/rest_framework/views.py", line 465, in handle_exception
self.raise_uncaught_exception(exc)
File "/lib/python3.6/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
raise exc
File "/lib/python3.6/site-packages/rest_framework/views.py", line 493, in dispatch
self.initial(request, *args, **kwargs)
File "/lib/python3.6/site-packages/rest_framework/views.py", line 410, in initial
self.perform_authentication(request)
File "/lib/python3.6/site-packages/rest_framework/views.py", line 324, in perform_authentication
request.user
File "/lib/python3.6/site-packages/rest_framework/request.py", line 220, in user
self._authenticate()
File "/lib/python3.6/site-packages/rest_framework/request.py", line 373, in _authenticate
user_auth_tuple = authenticator.authenticate(self)
File "/lib/python3.6/site-packages/rest_framework_jwt/authentication.py", line 33, in authenticate
payload = jwt_decode_handler(jwt_value)
File "/lib/python3.6/site-packages/rest_framework_jwt/utils.py", line 105, in jwt_decode_handler
secret_key = jwt_get_secret_key(unverified_payload)
File "/lib/python3.6/site-packages/rest_framework_jwt/utils.py", line 26, in jwt_get_secret_key
user = User.objects.get(pk=payload.get('user_id'))
File "/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/lib/python3.6/site-packages/django/db/models/query.py", line 431, in get
self.model._meta.object_name
From the JWT token, you are decoding it to get the user_id - payload['user_id'].
The error is happening because of User.objects.get(pk=payload.get('user_id')).
Instead of doing a get, you could use a get_object_or_404. Use it like so:
from django.shortcuts import get_object_or_404
payload = jwt_decode_handler(jwt_value)
user = get_object_or_404 (User, pk=payload.get('user_id'))
This raises a 404 error when a user will not be found; and that will be bubbled up through your view and handlers to return a 404 statuscode.
The suggestion by Druhn Bala works but would return a 404 error which isn't ideal for my use case. Instead I came up with one that returns a custom response. ValidationError
from rest_framework.exceptions allows you to send a 400 error with a custom response.
def jwt_decode_handler(token):
options = {
'verify_exp': api_settings.JWT_VERIFY_EXPIRATION,
}
# get user from token, BEFORE verification, to get user secret key
try:
unverified_user = jwt.decode(token, None, False)
except User.DoesNotExist:
raise ValidationError({"errors": ['Oops! Something went wrong, please logout and login back in!']})
secret_key = unverified_user.securitysettings.jwt_secret #my custom way of storing a unique jwt uuid per user.
return jwt.decode(
token,
api_settings.JWT_PUBLIC_KEY or secret_key,
api_settings.JWT_VERIFY,
options=options,
leeway=api_settings.JWT_LEEWAY,
audience=api_settings.JWT_AUDIENCE,
issuer=api_settings.JWT_ISSUER,
algorithms=[api_settings.JWT_ALGORITHM]
)
Lastly we set the custom decode handler as the default in settings.py.
JWT_AUTH = {
'JWT_DECODE_HANDLER':
'registration.decoder.jwt_decode_handler',
...
}

Django Swagger starts failing when include is used in django urls

I am using django rest_framework_swagger for my django project, everything was working fine but when I added some URLs with include method Swagger start giving me 500 internal server error.
I am not sure why this error is coming, I have checked but didn't find anything to fix this error.
I am using:
django 1.11.7
rest_framework_swagger 2.1.2
django rest framework 3.7.3
URLs
from django.conf.urls import url, include
from link_one.views import LinkOneViewSet
from link_two.views import LinkTwoViewSet
schema_view = get_swagger_view(title='My Project APIs')
urlpatterns = [
url(r'^$', schema_view),
url(r'^foo/(?P<foo_id>\w+)/bar/(?P<bar_id>\w+)/link1',
LinkOneViewSet.as_view({'get': 'list'})),
url(r'^foo/(?P<foo_id>\w+)/bar/(?P<bar_id>\w+)/link2',
LinkTwoViewSet.as_view({'get': 'list'})),
url(r'^foo/(?P<foo_id>\w+)/bar/(?P<bar_id>\w+)/link3',
include('link_three.urls'))
]+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Error
[25/Jan/2021 14:03:31] ERROR [django.request.exception:135] Internal Server Error: /
Traceback (most recent call last):
File "C:\Users\myuser\conda_env\lib\site-packages\django\core\handlers\exception.py", line 41, in inner
response = get_response(request)
File "C:\Users\myuser\conda_env\lib\site-packages\django\core\handlers\base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\myuser\conda_env\lib\site-packages\django\core\handlers\base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\myuser\conda_env\lib\site-packages\django\views\decorators\csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "C:\Users\myuser\conda_env\lib\site-packages\django\views\generic\base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\views.py", line 489, in dispatch
response = self.handle_exception(exc)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\views.py", line 449, in handle_exception
self.raise_uncaught_exception(exc)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\views.py", line 486, in dispatch
response = handler(request, *args, **kwargs)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework_swagger\views.py", line 32, in get
schema = generator.get_schema(request=request)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\schemas\generators.py", line 278, in get_schema
links = self.get_links(None if public else request)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\schemas\generators.py", line 316, in get_links
link = view.schema.get_link(path, method, base_url=self.url)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\schemas\inspectors.py", line 179, in get_link
fields += self.get_serializer_fields(path, method)
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\schemas\inspectors.py", line 302, in get_serializer_fields
serializer = view.get_serializer()
File "C:\Users\myuser\conda_env\lib\site-packages\rest_framework\generics.py", line 112, in get_serializer
return serializer_class(*args, **kwargs)
TypeError: 'list' object is not callable
After searching a lot and debugging, I have found a solution for this.
The solution is, don't use multiple serializer classes for a ViewSet.
In my one viewset I was doing this and this is what creating the problem.
class FooBarViewset(ModelViewSet):
serializer_class = [DefaultSerializer, BarSerializer, FooSerializer]
But I did not realize that this will cause the error.
Here is Fix that I am using
class FooBarViewset(ModelViewSet):
serializer_class = DefaultSerializer
You can also use the get_serializer method and map serializer class with an action, please check this answer Django rest framework, use different serializers in the same ModelViewSet

"No data received" with custom handler403 in django

When setting a simple handler403 for Django:
from django.conf.urls import url
from django.core.exceptions import PermissionDenied
from django.views.generic import TemplateView
class PermissionDeniedView(TemplateView):
template_name = '403.html'
handler403 = PermissionDeniedView.as_view()
def my_view(request):
raise PermissionDenied
urlpatterns = [
url(r'^$', my_view),
]
The browser does not receive any data (ERR_EMPTY_RESPONSE in chrome), and some errors appear on the log:
Traceback (most recent call last):
File "/usr/lib64/python2.7/wsgiref/handlers.py", line 86, in run
self.finish_response()
File "/usr/lib64/python2.7/wsgiref/handlers.py", line 127, in finish_response
for data in self.result:
File "/home/foo/.virtualenvs/bar/lib/python2.7/site-packages/django/template/response.py", line 171, in __iter__
raise ContentNotRenderedError('The response content must be '
ContentNotRenderedError: The response content must be rendered before it can be iterated over.
[20/May/2015 07:26:25]"GET / HTTP/1.1" 500 59
Traceback (most recent call last):
File "/usr/lib64/python2.7/SocketServer.py", line 599, in process_request_thread
self.finish_request(request, client_address)
File "/usr/lib64/python2.7/SocketServer.py", line 334, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/home/foo/.virtualenvs/bar/lib/python2.7/site-packages/django/core/servers/basehttp.py", line 102, in __init__
super(WSGIRequestHandler, self).__init__(*args, **kwargs)
File "/usr/lib64/python2.7/SocketServer.py", line 655, in __init__
self.handle()
File "/home/foo/.virtualenvs/bar/lib/python2.7/site-packages/django/core/servers/basehttp.py", line 182, in handle
handler.run(self.server.get_app())
File "/usr/lib64/python2.7/wsgiref/handlers.py", line 92, in run
self.close()
File "/usr/lib64/python2.7/wsgiref/simple_server.py", line 33, in close
self.status.split(' ',1)[0], self.bytes_sent
AttributeError: 'NoneType' object has no attribute 'split'
TemplateView returns a TemplateResponse instance with lazy content rendering by default, and is not suitable as is for handler403.
To force this view to render it's content, make sure .render() is called before returning the response:
class PermissionDeniedView(TemplateView):
template_name = '403.html'
def dispatch(self, request, *args, **kwargs):
response = super(PermissionDeniedView, self).dispatch(request, *args, **kwargs)
response.render()
return response

Programmatically downloading images and uploading them using ImageField via Boto

A did a function that would download the avatar of the user from his social network and uses it as our site avatar. Am using django 1.5 and boto with S3 storage for uploaded media..
The function I did works perfectly when running local, but for some reason when running on boto its throwing an exception. Below is the code am using
utils.py
# -*- coding: utf-8 -*-
import json
import urllib2
import requests
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
def download_photo(url):
"""
"""
r = requests.get(url)
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(r.content)
img_temp.flush()
return File(img_temp)
def graph_fb_profile_image(fb_uid, size=48):
url = 'http://graph.facebook.com/%s?fields=picture.height(%s).width(%s)' % (fb_uid, size, size)
request = urllib2.Request(url)
protocol = urllib2.build_opener()
response = protocol.open(request)
resp_json = json.load(response)
avatar = resp_json.get('picture').get('data').get('url')
return avatar
views.py
# get user avatar from facebook
avatar_url = graph_fb_profile_image(kwargs['response'].get('id'), 320)
user.avatar_src.save('avatar_%s.jpg' % (user.id,), download_photo(avatar_url), save=True)
The stack error am getting is following
Traceback (most recent call last):
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 115, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/newrelic-1.11.0.55/newrelic/api/object_wrapper.py", line 216, in __call__
self._nr_instance, args, kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/newrelic-1.11.0.55/newrelic/hooks/framework_django.py", line 475, in wrapper
return wrapped(*args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 77, in wrapped_view
return view_func(*args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/decorators.py", line 29, in wrapper
return func(request, request.social_auth_backend, *args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/views.py", line 42, in complete
return complete_process(request, backend, *args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/views.py", line 111, in complete_process
user = auth_complete(request, backend, *args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/views.py", line 196, in auth_complete
*xargs, **xkwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/backends/__init__.py", line 373, in continue_pipeline
return authenticate(*args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/django/contrib/auth/__init__.py", line 59, in authenticate
user = backend.authenticate(**credentials)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/backends/__init__.py", line 107, in authenticate
out = self.pipeline(pipeline, *args, **kwargs)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/social_auth/backends/__init__.py", line 136, in pipeline
result = func(*args, **out) or {}
File "/var/www/snowflake-env/snowflake/snowflake/apps/accounts/pipeline.py", line 45, in set_user_details
user.avatar_src.save('avatar_%s.jpg' % (user.id,), download_photo(avatar_url), save=True)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/django/db/models/fields/files.py", line 86, in save
self.name = self.storage.save(name, content)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/django/core/files/storage.py", line 48, in save
name = self._save(name, content)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/storages/backends/s3boto.py", line 282, in _save
reduced_redundancy=self.reduced_redundancy)
File "/var/www/snowflake-env/local/lib/python2.7/site-packages/boto/s3/key.py", line 1112, in set_contents_from_file
raise AttributeError('fp is at EOF. Use rewind option '
AttributeError: fp is at EOF. Use rewind option or seek() to data start.
any advise would be appreciated.
OK, I found an answer, I needed to add seek(0) to my function and make it as following
def download_photo(url):
"""
"""
r = requests.get(url)
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(r.content)
img_temp.flush()
img_temp.seek(0)
return File(img_temp)
this solved the problem for me