django rest framework create json and csv api - django

i am trying to create 2 api's. 1 to access json using ajax datatable, and the other to create an export csv button.
the json api i manage to create and get the data from it using ajax, but i get problems when i try to access the api in the browser and the other to add the csv api..i will show you part of my scripts and the errors i get.
views.py
class VideoViewSet(viewsets.ModelViewSet):
queryset = DatamsVideos.objects.all()
serializer_class = VideosSerializer
parser_classes = (CSVParser,) + tuple(api_settings.DEFAULT_PARSER_CLASSES)
renderer_classes = (CSVRenderer,) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
def list(self, request, **kwargs):
try:
vs = query_vs_by_args(**request.query_params)
serializer = VideosSerializer(vs['items'], many=True)
result = dict()
result['data'] = serializer.data
result['draw'] = vs['draw']
result['recordsTotal'] = vs['total']
result['recordsFiltered'] = vs['count']
return Response(result, status=status.HTTP_200_OK, template_name=None, content_type=None)
except Exception as e:
return Response(e, status=status.HTTP_404_NOT_FOUND, template_name=None, content_type=None)
def get_renderer_context(self):
context = super(VideoViewSet, self).get_renderer_context()
context['header'] = (
self.request.GET['fields'].split(',')
if 'fields' in self.request.GET else None)
return context
#action(methods=['GET'], detail=False)
def bulk_upload(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_303_SEE_OTHER, headers={'Location': reverse('videos-api')})
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'videos', VideoViewSet)
urlpatterns = [
url(r'^$', index, name='main'),
url(r'^api/', include(router.urls)),
]
and this is part of my models.py
def query_vs_by_args(**kwargs):
draw = int(kwargs.get('draw', None)[0])
length = int(kwargs.get('length', None)[0])
start = int(kwargs.get('start', None)[0])
search_value = kwargs.get('search[value]', None)[0]
order_column = kwargs.get('order[0][column]', None)[0]
order = kwargs.get('order[0][dir]', None)[0]
order_column = ORDER_VS_COLUMN_CHOICES[order_column]
# django orm '-' -> desc
if order == 'desc':
order_column = '-' + order_column
queryset = DatamsVideos.objects.all()
total = queryset.count()
if search_value:
queryset = queryset.filter(Q(link__icontains=search_value) |
Q(title__icontains=search_value) |
Q(views__icontains=search_value) | Q(date_added__icontains=search_value))
count = queryset.count()
queryset = queryset.order_by(order_column)[start:start + length]
return {
'items': queryset,
'count': count,
'total': total,
'draw': draw
}
1 of the problem i have is with the function list from views.py at
vs = query_vs_by_args(**request.query_params)
serializer = VideosSerializer(vs['items'], many=True)
i still manage to get the data from it using ajax, but when i try to access the api on the browser /main/api/videos/ it gives me this error:
HTTP 404 Not Found
Allow: GET, POST, HEAD, OPTIONS
Content-Type: text/csv ;utf-8
Vary: Accept
""
'NoneType' object has no attribute '__getitem__'
Same when i try to access /main/api/videos/?format=json
TypeError("'NoneType' object has no attribute '__getitem__'",) is not JSON serializable
When i try to get the csv from /main/api/videos/?format=csv, It Tells me File Not Found
I might be missing some details, can't just let me know if you want more information.
Thank you in advance
UPDATE
Now i just realize if i comment the function list the api's works fine..but my ajax is not working anymore because i don't have the 4 necessary things for to build the json
result['data'] = serializer.data
result['draw'] = ch['draw']
result['recordsTotal'] = ch['total']
result['recordsFiltered'] = ch['count']

I manage to fix the csv and api problem, by separating the classes.
class VideoCSVExportView(viewsets.ModelViewSet):
queryset = DatamsPornhubVideos.objects.all()
serializer_class = VideosSerializer
renderer_classes = (CSVRenderer,) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
after i create this new class for csv, i put it on a different route.
csv_router = DefaultRouter()
csv_router.register(r'videos', VideoCSVExportView)
...
url(r'^csv/', include(csv_router.urls)),
..

Related

Django, updating DB column, rest_framework

A bit stuck at updating column in DB. I am sending put request to update a column. But an error returns.
assert isinstance(response, HttpResponseBase), ( AssertionError:
Expected a Response, HttpResponse or HttpStreamingResponse to be
returned from the view, but received a <class 'NoneType'>
here is the front end. sending request.
updateInvoceProject(id, company_name){
return axios.put(API_HANDLER.database_api + "api/v1/update-project", {id, company_name})
},
serializer.py
class InvocesSerializer(serializers.ModelSerializer):
class Meta:
model = Invoces
fields = ("__all__")
view
#csrf_exempt
#api_view(["PUT"])
def update(request):
if request.method == "PUT":
serializer = InvocesSerializer(data=request.data)
if serializer.is_valid():
invoce = Invoces.objects.get(id=serializer.data["id"])
invoce.company_name = serializer.data["company_name"]
invoce.save()
return Response(serializer.data)
urls
urlpatterns = [
#
path("api/v1/update-project", invocesView.update, name="update-project"),
#
]
But in the end, the error I mentioned above is popping. Am I missing something here?
This happens because you are not returning anything when the serialized data is not valid.
you can simply make it like this, to make sure that it is going to return something in all cases.
#csrf_exempt
#api_view(["PUT"])
def update(request):
# if request.method == "PUT":
# You don't have to check for method, since you already defined it
# in api_view(...) decorator.
serializer = InvocesSerializer(data=request.data)
# Raises a ValidatinException which will be sent as a 400 response.
serializer.is_valid(raise_exception=True)
invoce = Invoces.objects.get(id=serializer.data["id"])
invoce.company_name = serializer.data["company_name"]
invoce.save()
return Response(serializer.data)
Better Solution
I suggest that you use DRF's UpdateAPIView (Do it the DRF way :D), in order to avoid getting into such errors, and also to avoid doing all the validation, and serialization by hand.
like the following:
1. Keep your InvocesSerializer, and create another one just for Updating company_name
# Inside serializers.py
class InvocesCompanyNameUpdateSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
return InvocesSerializer(instance).to_representation(instance)
class Meta:
model = Invoces
fields = ('company_name',)
2 . Create an UpdateAPIView class for that serializer
# Inside views.py
class InvoiceUpdateCompanyNameAPIView(UpdateAPIView):
http_method_names = ['put'] # This is only to allow PUT method on this view.
serializer_class = InvocesCompanyNameUpdateSerializer
queryset = Invoces.objects.all()
3 . Now append that view with a re_path to your urls.
# Inside urls.py
urlpatterns = [
#
# you have to add "pk" url path variable, so DRF use it internally to identify
# which object you want to update.
re_path(r"api/v1/update-project/(?P<pk>[\d]+)/",
invocesView.InvoiceUpdateCompanyNameAPIView.as_view(),
name="update-project"),
#
]

Apply pagination to Raw query to prevent ALL records being loaded

I have the following class based view that works as expected and paginates the results as required with parameters http://127.0.0.1:8000/api/collection/fromdetroit?page=1 >> ?page=2 etc
class ListReleasesCollectionView(APIView):
def get(self, request, format=None, *args, **kwargs):
try:
releases = ReleasesAll.objects.raw('SELECT releases.*, \'\' as num FROM releases_all releases INNER JOIN release_artists ra ON ra.release_id=releases.id LEFT JOIN genre_artists ON genre_artists.artist=ra.artists LEFT JOIN genre_labels ON genre_labels.label=releases.label_no_country WHERE genre_artists.genre=%s OR genre_labels.genre=%s GROUP by releases.id ORDER BY releases.date DESC',(kwargs['collection'],kwargs['collection']))
paginator = PageNumberPagination()
result_page = paginator.paginate_queryset(releases, request)
serializer = ReleasesSerializer(result_page, many=True, context={'request': request})
response = Response({'results':{'image_url':'', 'page_header':'','releases': serializer.data}}, status=status.HTTP_200_OK)
return response
except ReleasesAll.DoesNotExist:
return Response(
data = {
"message": "{} does not exist".format(kwargs["collection"])
},
status=status.HTTP_404_NOT_FOUND
)
However, it runs incredibly slowly because it has to download ALL results first and then paginate after. Here are the results from Django Toolbar.
I would like to only download 60 results at a time as there are thousands of results returned above.
Pagination settings in settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10
}
Add your pagination class. create pagination_class.py.
from rest_framework.pagination import PageNumberPagination
class StandardResultsSetPagination(PageNumberPagination):
page_size_query_param = 'limit'
Add this into settings.py.
REST_FRAMEWORK = {
...
'DEFAULT_PAGINATION_CLASS': 'app_name.pagination_class.StandardResultsSetPagination',
...
}

Django REST Framework - unittest client failing to resolve hyperlinks relation for POST

I have this test:
class AttributeTest(APITestCase):
def setUp(self):
user1 = User.objects.create(pk=1, username='pepa', email='ads#asasd.cz', is_active=True, is_staff=True)
user1.set_password('mypass')
user1.save()
self.c1 = Campaign.objects.create(pk=1, owner=user1, project_name='c1')
def test(self):
campaign_url = 'http://testserver/api/campaigns/{}/'.format(self.c1.pk)
self.client.login(username='pepa', password='mypass')
data = {
"label": "something_here",
"parent_campaign": campaign_url,
}
# campaign clearly exists (created in setUp) and GET retrieve it:
assert self.client.get(campaign_url).json()['project_name'] == 'c1'
# I can even try it myself using pdb
# but this doesn't work - response return 400 Bad Request
# complaining about the very same hyperlink I can GET above
response = self.client.post('/api/keys', data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
but when run, it fails with {'parent_campaign': ['Invalid hyperlink - No URL match.']}.
When I try using curl or browsable API (outside the test environment), everything works as expected.
My serializer corresponding to the /api/keys:
class AttributeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='api:key-detail')
parent_campaign = serializers.HyperlinkedRelatedField(
view_name='api:campaign-detail',
lookup_field='cid',
queryset=Campaign.objects.all())
def _get_user_campaigns(self):
user = self.context['view'].request.user
return Campaign.objects.filter(owner=user)
def get_fields(self, *args, **kwargs):
fields = super(AttributeSerializer, self).get_fields(*args, **kwargs)
fields['parent_campaign'].queryset = self._get_user_campaigns()
return fields
class Meta:
model = Key
fields = ("id", 'url', "label", 'parent_campaign')
Using serializer directly:
(Pdb) from api.attribute.serializers import AttributeSerializer
(Pdb) ser = AttributeSerializer(data=data)
(Pdb) ser.is_valid()
True
(Pdb) ser.save()
<Key: Something1 | MAROO | CID: lrvyw93>
Try reversing your url name and passing c1.pk as a url parameter, not just formatting it into your url:
from rest_framework.reverse import reverse
campaign_url_name = 'api:campaign-detail' # Use URL name instead of raw URL path
response = self.client.get(reverse(campaign_url_name, kwargs={'pk': self.c1.pk}))
I don't know why, but the results of tests had to be somehow cached. I restarted the PC and it worked with exactly the same commit. Solved.

How to paginate response from function based view of django rest framework?

I have written a code snippet like below, i need to achieve pagination in this, kindly let me know how is it possible. Also due to some reasons i want to use only function based views.
#api_view(['GET'])
#permission_classes([AllowAny])
def PersonView(request):
context={'request': request}
person_objects = Person.objects.all()
if len(person_objects) > 0:
person_data = PersonSerializer(person_objects, many=True, context=context)
return Response(person_data.data, status=status.HTTP_200_OK)
else:
return Response({}, status=status.HTTP_200_OK)
http://www.django-rest-framework.org/api-guide/pagination/
from rest_framework.pagination import PageNumberPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
paginator = PageNumberPagination()
paginator.page_size = 10
person_objects = Person.objects.all()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
You can also define custom pagination class by overriding PageNumberPagination
pagination.py
from rest_framework import pagination
class StandardResultsSetPagination(pagination.PageNumberPagination):
page_size = 10
page_query_param = 'page'
page_size_query_param = 'per_page'
max_page_size = 1000
it will help to define
page_size, page query custom parameters and max_page_size
views.py
from rest_api.pagination import StandardResultsSetPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
person_objects = Person.objects.all()
if len(person_objects)> 0:
paginator = StandardResultsSetPagination()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
else:
return Response({},status=status.HTTP_200_OK)
Eg:
Request
GET https://api.example.org/persons/?page=1&per_page=10
Response
HTTP 200 OK
{
"count": 1023
"next": "https://api.example.org/persons/?page=2&per_page=10",
"previous": null,
"results": [
…
]
}
The ACCEPTED answer has a BUG
A ModelSerializer accepts a queryset or an object, in the accepted answer the PersonSerializer is given the ouput of paginator.paginate_queryset which returns a list containing single element of class Page and thus the serializer will either return incomplete data or no data at all(know this because have tried and seen wrong results).
This can be easily fixed by passing the actual queryset to the serializer which in this case will be person_objects,
So the final code will be,
from rest_framework.pagination import PageNumberPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
paginator = PageNumberPagination()
paginator.page_size = 10
person_objects = Person.objects.all()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(person_objects, many=True) # MAIN CHANGE IS HERE
return paginator.get_paginated_response(serializer.data)
This fixes the bug but passing the complete queryset to serializer will take lot of time for serializing since serializer works on lazy approach, but this performance issue is in itself a new question.

Adding root element to json response (django-rest-framework)

I am trying to determine the best way to add a root element to all json responses using django and django-rest-framework.
I think adding a custom renderer is the best way to accomplish what I want to achieve and this is what I have come up with so far:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
#override the render method
def render(self, data, accepted_media_type=None, renderer_context=None):
#call super, as we really just want to mess with the data returned
json_str = super(CustomJSONRenderer, self).render(data, accepted_media_type, renderer_context)
root_element = 'contact'
#wrap the json string in the desired root element
ret = '{%s: %s}' % (root_element, json_str)
return ret
The tricky part now is dynamically setting the root_element based on the view that render() is being called from.
Any pointers/advice would be greatly appreciated,
Cheers
For posterity, below is the final solution. It has grown slightly from the original as it now reformats paginated results as well.
Also I should have specified before, that the reason for the JSON root element is for integration with an Ember front end solution.
serializer:
from rest_framework.serializers import ModelSerializer
from api.models import Contact
class ContactSerializer(ModelSerializer):
class Meta:
model = Contact
#define the resource we wish to use for the root element of the response
resource_name = 'contact'
fields = ('id', 'first_name', 'last_name', 'phone_number', 'company')
renderer:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
"""
Override the render method of the django rest framework JSONRenderer to allow the following:
* adding a resource_name root element to all GET requests formatted with JSON
* reformatting paginated results to the following structure {meta: {}, resource_name: [{},{}]}
NB: This solution requires a custom pagination serializer and an attribute of 'resource_name'
defined in the serializer
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
#determine the resource name for this request - default to objects if not defined
resource = getattr(renderer_context.get('view').get_serializer().Meta, 'resource_name', 'objects')
#check if the results have been paginated
if data.get('paginated_results'):
#add the resource key and copy the results
response_data['meta'] = data.get('meta')
response_data[resource] = data.get('paginated_results')
else:
response_data[resource] = data
#call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
pagination:
from rest_framework import pagination, serializers
class CustomMetaSerializer(serializers.Serializer):
next_page = pagination.NextPageField(source='*')
prev_page = pagination.PreviousPageField(source='*')
record_count = serializers.Field(source='paginator.count')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
# Takes the page object as the source
meta = CustomMetaSerializer(source='*')
results_field = 'paginated_results'
Credit to ever.wakeful for getting me 95% of the way there.
Personally, I wanted to add meta data to every api request for a certain object, regardless of whether or not it was paginated. I also wanted to simply pass in a dict object that I defined manually.
Tweaked Custom Renderer
class CustomJSONRenderer(renderers.JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
# Name the object list
object_list = 'results'
try:
meta_dict = getattr(renderer_context.get('view').get_serializer().Meta, 'meta_dict')
except:
meta_dict = dict()
try:
data.get('paginated_results')
response_data['meta'] = data['meta']
response_data[object_list] = data['results']
except:
response_data[object_list] = data
response_data['meta'] = dict()
# Add custom meta data
response_data['meta'].update(meta_dict)
# Call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
Parent Serializer and View Example
class MovieListSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
meta_dict = dict()
meta_dict['foo'] = 'bar'
class MovieViewSet(generics.ListAPIView):
queryset = Movie.objects.exclude(image__exact = "")
serializer_class = MovieListSerializer
permission_classes = (IsAdminOrReadOnly,)
renderer_classes = (CustomJSONRenderer,)
pagination_serializer_class = CustomPaginationSerializer
paginate_by = 10