Calling viewset from another route - python

I use Django rest-framework which fetch the items from tables and return serialized json, when calling like this below
localhost/api/mixs?id=12
Source code.
class MixViewSet(viewsets.ModelViewSet):
serializer_class = MixSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
filter_fields = ["id","user"]
filterset_fields = ['id']
search_fields = ['id']
def list(self,request,*args,**kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
custom_data = {
'items': serializer.data
}
custom_data.update({
'meta':{"api":"Mix"}
})
return Response(custom_data)
def get_queryset(self):
queryset = Mix.objects.all()
ids = self.request.query_params.get('ids')
if ids is not None:
id_arr = ids.split(',')
if len(id_arr) > 0:
queryset = queryset.filter(id__in=id_arr)
u_key = self.request.query_params.get('u_key')
if u_key is not None:
queryset = queryset.filter(u_key=u_key)
return queryset
Now, I want to use this function from another method.
For example
def createMix(request):
#do something and make Mix
m = Mix(detail={},user=1)
m.save()
print(m.id) ### for example 13
#Now I want to do the equivalent thing
#to `localhost/api/mixs?id=13`
# returning the data id=13
obj = Mix.objects.get(id=m.id)
response = MixSerializer(obj)
print(response)
return Response(response)
#AssertionError: You passed a Serializer instance as data, but probably meant to pass serialized `.data` or `.error`. representation
When calling this url
localhost/myapp/createsong
The program do something and insert data in Mix table.
then do the equivalent thing to localhost/api/mixs?id=13
Is it possible?
Or my idea is correct?

You can probaly do something like this. There is nothing restricts you from using test client as part of code.
from django.test.client import Client
c = Client()
article = c.post('/api/mixes', {
'id' : 13,
})

Related

DRF: Filter several fields in one request

I have to implement set of filters (see picture). My code works fine for 1 filter e.g.
http://127.0.0.1:8000/api/constructions?field=developer&value=1 -- filter by developer with id =1
I want to filter by several filters in one request.
I can use something like this
http://127.0.0.1:8000/api/constructions?field=field1_field2&value=value1_value2
Split field1_field2 --> [field1, field2] and so on (it's not perfect)
Is there better way to solve my issue?
views.py
class ConstructionView(viewsets.ModelViewSet):
serializer_class = ConstructionSerializer
queryset = Construction.objects.all()
pagination_class = BasePagination
def list(self, request):
field = request.GET.get('field', None)
value = request.GET.get('value', None)
if field is not None and value is not None:
queryset = Construction.objects.filter(**{field:value})
else:
queryset = Construction.objects.all()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = ConstructionSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
else:
serializer = ConstructionSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
If you have a list at your query_params you can use that approach. You can check if your value of your query_params is a list or string and apply it to a filter. <fieldname>__in works for list.
custom_filter = {'field__in': request.query_params.get('field'), 'value__in': request.query_params.get('value')}
queryset = Construction.objects.filter(**custom_filter )
Maybe djangorestframework-queryfields helps your for a bunch of common work.
If you want to get multiple object by their ID, you can try something like this:
Construction.objects.filter(id__in = [1,2])
It will return objects with 1,2 IDs

how can insert multiple record using

I'm working on a small project Django Rest Framework, I already create add contact function as you can see in my create function. now I'm working on bulk import, but when I submit my data as a list not as a dict I get an error message :
{"non_field_errors":["Invalid data. Expected a dictionary, but got list."]}
this is my code to add a contact,
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
serializeObject = ContactSerializer(data = request.data)
if serializeObject.is_valid():
serializeObject.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
Now i would like to create another function, for bulk create, since i have a list
This is my header data structure :
[{"Greeting":"amine","first_name":"alain","last_name":"amine","title":"ricardo","language":"ab#xyz.com","email":43822510594,"phone_1":43822510594,"phone_2":"not yet","mobile":43822510594,"fax":"not yet","internal_id":"xname"},{"Greeting":"bill","first_name":"microsoft","last_name":"bill","title":"microsoft","language":"bill#microsoft.com","email":652565455,"phone_1":652565455,"phone_2":"new york","mobile":652565455,"fax":"new york","internal_id":"microsoft"},{"Greeting":"john","first_name":"Yoyo","last_name":"Ruth","title":"xnameagain","language":"rh#xyz.com","email":5465559852,"phone_1":5465559852,"phone_2":"Vancouver","mobile":5465559852,"fax":"Vancouver","internal_id":"yname"}]
This is my serializer:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
I found the Solution on https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects
all what i have to do is to add many=True to create multiple object
serializeObject = ContactSerializer(data = request.data, many=True)
Create method should look like this:
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
valid_objects = []
for data in request.data:
serializeObject = ContactSerializer(data=data)
if serializeObject.is_valid():
valid_objects.append(serializeObject)
else:
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
for obj in valid_objects:
obj.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
Advise
They may not be the best practices but it works.

fetch ids with commas in django

view.py
def get(self, *args, **kwargs):
res = Question.objects.all()
# queryset = super().ListAPIView(*args, **kwargs)
if 'tag' in self.request.GET:
# split and directly put in main query
res = res.filter(
Tag_name=self.request.GET['tag']
)
if 'order_by' in self.request.GET:
res = res.order_by(
self.request.GET['order_by'])
serializer = QuestionSerializers(res, many=True)
return Response(serializer.data)
here I am trying to fetch question with some input tag ids and order by some input , how can I use multiple tag ids in url like
http://127.0.0.1:8000/?tag=1,2&order_by=name
so I get all objects with tag ids 1 and 2.
You can pass multiple get parameters with the same name:
http://127.0.0.1:8000/?tag=1&tag=2&order_by=name
In your view you can access the list using the getlist method:
def get(self, *args, **kwargs):
# returns empty list if no tags are provided
tags = self.request.GET.getlist('tag')
# you can set a default field to order_by, if no field is provided
order_by = self.request.GET.get('order_by', 'id')
# convert tags, currently strings, to int
tags = [int(tag) for tag in tags]
# not sure about this part would need to check your models
res = Question.objects.filter(tag__id__in=tags).order_by(order_by)
serializer = QuestionSerializers(res, many=True)
return Response(serializer.data)
You need to split values of tag by , and filter all tags with id__in filter like this
def get(self, *args, **kwargs):
res = Question.objects.all()
# queryset = super().ListAPIView(*args, **kwargs)
if self.request.GET.get('tag'):
# split and directly put in main query
res = res.filter(
tag_id__in=self.request.GET.get('tag').split(',')
)
if self.request.GET.get('order_by'):
res = res.order_by(
self.request.GET['order_by'])
serializer = QuestionSerializers(res, many=True)
return Response(serializer.data)
Well you can have a workaround(or better to say more django rest framework oriented way to achieve this) using ListAPIView of DRF like:
from rest_framework.generics import ListAPIView
class TagListAPIView(ListAPIView):
serializer_class = QuestionSerializers
filter_backends = (OrderingFilter)
ordering_fields = ['name', ...any other fields you wanna put here as an option for ordering]
ordering = 'name' #setting the default ordering
def get_queryset(self):
ids = self.request.query_params.get('tag', None)
if not ids:
return Question.objects.none() #Empty queryset
return Question.objects.filter(id__in=ids.split(','))
Haven't tested the code but yeah it should be somewhere around this and this code does not do all the proper input handling if some garbage input is coming from user(which is the case manytimes).
You could write more strong code for this by incorporating django-filters and putting a id field in that filter. Which would give you all the error validation and handling.
I have provided a links to DRF's doc regarding filter, ordering and all. https://www.django-rest-framework.org/api-guide/filtering/#orderingfilter
Have a look, its great content.
Ping me if anything unclear.

Django REST Framework how to add context to a ViewSet

The ViewSets do everything that I want, but I am finding that if I want to pass extra context to a template (with TemplateHTMLRenderer) then I will have to get at the functions that give responses.. (like list(), create(), etc)
The only way I can see to get into these is to completely redefine them in my ViewSet, but it seems that there should be an easy way to add a bit of context to the Template without having to redefine a whole set of methods...
class LanguageViewSet(viewsets.ModelViewSet):
"""Viewset for Language objects, use the proper HTTP methods to modify them"""
# TODO: add permissions for this view?
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
Right now my code is looking like this but I will be wanting to add different context to the responses and I am trying to avoid redefining an entire method for such a small change. like this...
class LanguageViewSet(viewsets.ModelViewSet):
"""Viewset for Language objects, use the proper HTTP methods to modify them"""
# TODO: add permissions for this view?
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
def list(self, **kwargs):
"""Redefinition of list"""
..blah blah everything that list does
return Response({"foo": "bar"}, template_name="index.html")
I faced the same issue and resolved in a slightly different way in Django Rest Framework (DRF) 3.x. I believe it is not necessary to override the relatively complex render method on the TemplateHTMLRenderer class, but only the much simpler method get_template_context (or: resolve_context in earlier versions of DRF).
The procedure is as follows:
Override the get_renderer_context method on your ViewSet (as already suggested):
def get_renderer_context(self):
context = super().get_renderer_context()
context['foo'] = 'bar'
return context
Subclass TemplateHTMLRenderer, but only override the get_template_context method instead of the entire render method (render calls self.get_template_context to retrieve the final context to pass to the template):
class ModifiedTemplateHTMLRenderer(TemplateHTMLRenderer):
def get_template_context(self, data, renderer_context):
"""
Override of TemplateHTMLRenderer class method to display
extra context in the template, which is otherwise omitted.
"""
response = renderer_context['response']
if response.exception:
data['status_code'] = response.status_code
return data
else:
context = data
# pop keys which we do not need in the template
keys_to_delete = ['request', 'response', 'args', 'kwargs']
for item in keys_to_delete:
renderer_context.pop(item)
for key, value in renderer_context.items():
if key not in context:
context[key] = value
return context
{{ foo }} is now available as a template variable - as are all other variables added in get_renderer_context.
Although I disagree with 'pleasedontbelong' in principle, I agree with him on the fact that the extra contextual data ought to be emitted from the serializer. That seems to be the cleanest way since the serializer would be returning a native python data type which all renderers would know how to render.
Heres how it would look like:
ViewSet:
class LanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
def get_serializer_context(self):
context = super().get_serializer_context()
context['foo'] = 'bar'
return context
Serializer:
class YourSerializer(serializers.Serializer):
field = serializers.CharField()
def to_representation(self, instance):
ret = super().to_representation(instance)
# Access self.context here to add contextual data into ret
ret['foo'] = self.context['foo']
return ret
Now, foo should be available inside your template.
Another way to achieve this, in case you don't wish to mess with your serializers, would be to create a custom TemplateHTMLRenderer.
class TemplateHTMLRendererWithContext(TemplateHTMLRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
# We can't really call super in this case, since we need to modify the inner working a bit
renderer_context = renderer_context or {}
view = renderer_context.pop('view')
request = renderer_context.pop('request')
response = renderer_context.pop('response')
view_kwargs = renderer_context.pop('kwargs')
view_args = renderer_context.pop('args')
if response.exception:
template = self.get_exception_template(response)
else:
template_names = self.get_template_names(response, view)
template = self.resolve_template(template_names)
context = self.resolve_context(data, request, response, render_context)
return template_render(template, context, request=request)
def resolve_context(self, data, request, response, render_context):
if response.exception:
data['status_code'] = response.status_code
data.update(render_context)
return data
To add data into the context, ViewSets provide a get_renderer_context method.
class LanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all()
serializer_class = LanguageSerializer
filter_backends = (filters.DjangoFilterBackend, )
filter_fields = ('name', 'active')
def get_renderer_context(self):
context = super().get_renderer_context()
context['foo'] = 'bar'
return context
{'foo': 'bar'} should now be available in your template.

Tastypie 'Search' Only Returns Resource Name

I'm trying to create a function that filters objects by a ForeingKey field (or rather the object being referenced in the field). I wrote the function which I have included below, which is based off the Tastypie Cookbook's Adding Search Functionality. The function does work in that it returns the objects, however it only returns the objects' names as a string.
This is the result of calling the function:
{'receipt_items': [<ReceiptItem: Item1>, <ReceiptItem: Item2>, <ReceiptItem: Item3>]}
Here is my resource:
class ReceiptItemResource(ModelResource):
receipt = fields.ToOneField(ReceiptResource, 'receipt', full = True)
class Meta:
queryset = ReceiptItem.objects.all()
serializer = Serializer()
allowed_methods = ['get', 'post', 'put', 'delete']
always_return_data = True
def prepend_urls(self):
return [
url(r'^(?P<resource_name>%s)/filter%s$' % (self._meta.resource_name, trailing_slash()), self.wrap_view('filter_by_receipt'), name = 'filter_by_receipt'),
]
def filter_by_receipt(self, request, **kwargs):
data = self.deserialize(request, request.body, format = request.META.get('CONTENT_TYPE', 'application/json'))
receipt_id = data.get('receipt_id', '')
print receipt_id
receipt = Receipt.objects.get(id = receipt_id)
receipt_items = ReceiptItem.objects.filter(receipt = receipt)
item_list = {
'receipt_items' : receipt_items,
}
print item_list
return self.create_response(request, receipt_items)
#return super(ReceiptItemResource, self).get_object_list(request).filter(receipt = receipt)
def get_object_list(self, request):
user = request.user
member = user.member
owner = member.owner
return super(ReceiptItemResource, self).get_object_list(request).filter(owner = owner)
Ideally I would like this function to return the full object details in JSON. Is there any way to make this happen?
I have looked into Tastypie's resource filtering, however I don't believe this would work since the field I am trying to filter by is a ForeignKey.
All help is greatly appreciated, thank you.
You should not need to do this. You can filter on foreignKey by default using the filtering option
from tastypie.resources import ALL, ALL_WITH_RELATIONS
class RecieptResource(ModelResource):
class Meta:
fields = ["id"]
filtering = {
"id": ALL
}
class ReceiptItemResource(ModelResource):
receipt = fields.ToOneField(ReceiptResource, 'receipt', full = True)
class Meta:
queryset = ReceiptItem.objects.all()
allowed_methods = ['get', 'post', 'put', 'delete']
always_return_data = True
filtering = {
"reciept": ALL_WITH_RELATIONS
}
Now the following should url (depending on how you have yours configured) should give you what you want
/api/v1/reciept-item/?receipt__id=1&format=json

Categories