Populating request.DATA for testing api in Django using APIRequestFactory - django

I am using restframeworks test modules to write test. I need to test a get request for which I created a test as follows
class BlogCommentTest(APISimpleTestCase):
def test_3002_getting_reviewcomment(self):
factory = APIRequestFactory()
request = factory.get(reverse('get-review-comments'), data= {"article": 1})
request.user = self.user
view = ReviewCommentViewSet.as_view({'get': 'list'})
force_authenticate(request, self.user)
response = view(request)
however the way I wrote populates the request.GET with {'article':1} when I want to populate request.DATA. What is the correct way to make the request object whose request.DATA is populated the way I want ?
EDIT
The code for viewset is
class ReviewCommentViewSet(viewsets.ModelViewSet):
"""
viewset to get recursive serialized results
"""
model = models.ReviewComments
serializer_class = serializers.ReviewCommentSerializer
queryset = models.ReviewComments.objects.filter(is_deleted=False)
def serialize_tree(self, queryset):
"""
:return: A helper function to get list of next set of childrens
"""
for obj in queryset:
data = self.get_serializer(obj).data
data['parent'] = self.serialize_tree(obj.children.all().exclude(is_deleted=True))
yield data
def list(self, request):
queryset = self.get_queryset().filter(parent=None, article=request.DATA['article'])
data = self.serialize_tree(queryset)
return Response(data)
def retrieve(self, request, pk=None):
self.object = self.get_object()
data = self.serialize_tree([self.object])
return Response(data)

Related

Django - How to change the default queryset in a view if a filter is used? [duplicate]

In ListView, I can easily use def post(self, request) method to make a post request from a list view. But I want to make the post request from def get_queryset(self) which I am not yet able to do. When I try to do it it shows "method 405 not allowed!" even though post method is allowed through http_method_names.
How can I access POST request inside get_queryset function?
class ZonListView(SearchMixin, SingleTableMixin, ListView):
template_name = 'cadmin/list.html'
model = Zon
table_class = ZonTable
search_fields = {
'title': 'icontains',
'description': 'icontains',
}
def post(self, request): # ***** this one works! ******
try:
toggle_status = request.POST.get('toggle-status')
pk = int(request.POST.get('pk'))
....
return HttpResponseRedirect(reverse('cadmin:zon_list'))
def get_queryset(self):
qs = super(ZonListView, self).get_queryset()
if self.request.POST: #***** Not working. 405 Error *****#
try:
toggle_status = self.request.POST.get('toggle-status')
pk = int(self.request.POST.get('pk'))
......
if self.request.GET:
try:
status = self.request.GET.get('status')
qs = qs.filter(status=status)
except Exception:
pass
return qs.distinct()
def get_context_data(self, **kwargs):
....
To make method allowed you need to implement function named same as method, post in your case. So to use request.POST in get queryset you also need to define post() method like this:
def post(self, request): # ***** this method required! ******
self.object_list = self.get_queryset()
return HttpResponseRedirect(reverse('cadmin:zon_list'))
def get_queryset(self):
qs = super(ZonListView, self).get_queryset()
if self.request.POST: #***** Now allowed *****#
try:
toggle_status = self.request.POST.get('toggle-status')
pk = int(self.request.POST.get('pk'))
......
Look Django's View source to check how allowed methods defined.

Avoid PUT response data to contain full data with nested serializer and generic views (Django Rest Framework)

I'm building an API based on a RetrieveUpdateAPIView with a Writable nested serializers. The issue I have is that when I make a PUT request, the response data contains all the data of the serializer... And the amount of those data is pretty big.
How can I modify my code to avoid this behavior? I imagine that this should be at the level of the view? I would preferably stick to generic views.
Thanks for your support.
For the record,
Here is the serializer code:
# DatetimeValue serializer
class DatetimeValueSerializer(serializers.ModelSerializer):
class Meta:
model = DatetimeValue
fields = ['date_time', 'value']
# Serializer that includes time series header and date time values
class TimeSeriesValueSerializer(serializers.ModelSerializer):
values = DatetimeValueSerializer(many=True)
class Meta:
model = TimeSeries
fields = ['id', 'name', 'values']
def update(self, instance, validated_data):
ts_name = validated_data.pop('name')
try:
time_series = TimeSeries.objects.get(name=ts_name)
except ObjectDoesNotExist:
raise ValidationError(_(f"Time series doesn't exist"))
values_data = validated_data.pop('values')
for datetime_value in values_data:
try:
DatetimeValue.objects.create(time_series=time_series, **datetime_value)
except IntegrityError:
raise ValidationError(_(f"ERROR in 'date_time' and/or 'value' provided. "
f"'date_time' has to be a correct date time format and 'value' a float. "
f"A 'date_time' has to be unique for a given time series"))
return time_series
And the view code:
# View to get time series datetime values
class TimeSeriesValueDetail(generics.RetrieveUpdateAPIView):
queryset = TimeSeries.objects.all()
serializer_class = TimeSeriesValueSerializer
You can modify the serialzier_class depending on request.method using get_serializer_class
class TimeSeriesValueDetail(generics.RetrieveUpdateAPIView):
queryset = TimeSeries.objects.all()
serializer_class = TimeSeriesValueSerializer
def get_serializer_class(self):
if self.request.method == 'PUT':
return ShortTimeSeriesSerializer
return super().get_serializer_class()
or you can return a custom data at the end of the put handler:
class TimeSeriesValueDetail(generics.RetrieveUpdateAPIView):
def put(self, request, *args, **kwargs):
self.update(request, *args, **kwargs)
return Response(data={'success':True})
or you can return some other data after the update:
class TimeSeriesValueDetail(generics.RetrieveUpdateAPIView):
...
def update(self, request, *args, **kwargs):
super().update(request, *args, **kwargs)
return Response(data={'success':True})

Django REST framework getstream notifications

I'm trying to follow the getstream docs for django with rest framework. https://github.com/GetStream/stream-django
Here is a minimal drf api cloned from the drf tutorial https://github.com/morenoh149/django-rest-framework-getstream
I get an error
TypeError at /notifications/
Object of type Feed is not JSON serializable
when opening /notifications/ in a browser. How do I write an endpoint to show the user's notifications?
views.py
class NotificationViewSet(viewsets.ViewSet):
"""
This viewset returns a notifications feed for the logged in user.
The feed contains events for when a relevant snippet is created.
"""
serializer_class = NotificationSerializer
def list(self, request):
user_id = request.user.id
notification_feed = feed_manager.get_notification_feed(user_id)
return Response(notification_feed)
serializers.py
class ActivitySerializer(serializers.Serializer):
id = serializers.UUIDField()
foreign_id = serializers.CharField()
verb = serializers.CharField()
time = serializers.DateTimeField()
def __init__(self, *args, **kwargs):
object_serializer = kwargs.pop("object_serializer", None)
actor_serializer = kwargs.pop("actor_serializer", None)
super().__init__(self, *args, **kwargs)
if object_serializer:
self.fields["object"] = object_serializer()
else:
self.fields["object"] = serializers.CharField()
if actor_serializer:
self.fields["actor"] = actor_serializer()
else:
self.fields["actor"] = serializers.CharField()
class AggregatedSerializer(ActivitySerializer):
group = serializers.CharField()
activities = ActivitySerializer(many=True)
class NotificationSerializer(AggregatedSerializer):
is_seen = serializers.BooleanField()
is_read = serializers.BooleanField()
Thanks JPG, you're correct. With a few more steps you can even get 'enriched' activities. Harry Moreno, check the example I provided on github:
https://github.com/pterk/django_twitter/blob/master/stream_twitter/views.py#L141 copied here for your convenience:
class TimelineAPIView(APIView):
"""
Timeline as an api view, enriched with local data
"""
def get(self, request):
feeds = feed_manager.get_news_feeds(self.request.user.id)
activities = feeds.get('timeline').get()['results']
enriched_activities = enricher.enrich_activities(activities)
serializer = get_activity_serializer(enriched_activities, TweetSerializer, None, many=True)
return Response(serializer.data)
I'm not sure about the code snippet inside the list() method. But, I think you are returning the Feed model instance to the Response class, but DRF expects some data of JSON type.
So, return the serialized version of Feed instances with the help of NotificationSerializer as below
class NotificationViewSet(viewsets.ViewSet):
"""
This viewset returns a notifications feed for the logged in user.
The feed contains events for when a relevant snippet is created.
"""
serializer_class = NotificationSerializer
def list(self, request):
user_id = request.user.id
notification_feed = feed_manager.get_notification_feed(user_id)
serializer = NotificationSerializer(notification_feed)
serializer.is_valid() # this has no effect in your code
return Response(serializer.data)

Post request handling in Django Rest framework

I am using Django Rest Framework, currently to pull some data from the backend we are using Get request, but due to URL limit going high we are planning to implement a Post request. To do this firstly the backend Django Rest API has to be made available to serve a post request.
I am new to Django and I don't find a post or get method in the code, all i can say is we are using viewsets, I tried using "#detail_route(methods=['post'])" but this didn't work, what am I doing wrong here?
class XViewSet(viewsets.ViewSet):
renderer_classes = ''
def retrieve(self, request, pk=None):
try:
pk = int(pk)
except ValueError:
raise InvalidParameterError(parameter_name='id', invalid_value=pk)
queryset = models.X.objects.all()
x = get_object_or_404(queryset, pk=pk)
pipelines = request.query_params.getlist('pipeline[]')
callsets =
callset_ids =
serializer = serializers.XSerializer(x, context={'request': request})
requested_samples = [z[1:] for z in request.query_params.getlist('s')]
filtered_calls = []
serialized_data = serializer.data
unfiltered_calls = serialized_data.get('calls')
if unfiltered_calls:
for serialized_calls in unfiltered_calls:
if serialized_calls['callset'] in callset_ids:
unfiltered_calls = serialized_calls['data']
for call in unfiltered_calls:
if call['y'] in requested_y:
filtered_calls.append(call)
break
serialized_data['calls'] = filtered_calls
return Response(serialized_data, status=status.HTTP_200_OK)
def list(self, request):
qp = self.request.query_params
validated_qp =
# generate the query
query_object =
query =
# execute the query
cursor = connections['default'].cursor()
cursor.execute(query)
qs = utils.dictfetchall(cursor)
# sanitize query results
if 't' in validated_qp:
return_data =
else:
for x in qs:
if 'calls' in x:
x['calls'] =
else:
x['calls'] = {}
return_data =
resp = Response(return_data, status=status.HTTP_200_OK)
if validated_qp.get(''):
resp['content-disposition'] =
return resp
You can use class-based views to handle the requirements,
from rest_framework.views import APIView
class MyAPI(APIView):
def get(selfself, request):
# do stuff with get
return Response(data="return msg or data")
def post(self, request):
post_data = request.data
# do something with `post_data`
return Response(data="return msg or data")
UPDATE : Using ViewSet
ViewSet class provide us create() methode to create new model instances. So we can override that to handle post data coming to the view. Just add a create() under your view class as below
class XViewSet(viewsets.ViewSet):
renderer_classes = ''
def create(self, request): # Here is the new update comes <<<<
post_data = request.data
# do something with post data
return Response(data="return data")
def retrieve(self, request, pk=None):
# your code
return Response(serialized_data, status=status.HTTP_200_OK)
def list(self, request):
# your code
return resp

How to use django-el-pagination with filtering?

I have a django Listview and i am using django-el-pagination's ajax endless pagination to paginate my results and it works well. The problem is I need to apply some filters to it and have no clue on how to do it.
Can i send parameters through GET to the view? Have searched a lot and seems like no one had this problem before.
Data is always returned in the get_queryset() function.
If you want to simply filter from the database, you can just return objects.
class IndexView(AjaxListView):
template_name = '****'
context_object_name = '****'
page_template = '****'
def get_queryset(self):
return your_model.objects.filter(title='***').order_by('**')
Else if you want to get data from non-database, you need to implement a proxy accordding to this answer.
If not,the pagination ajax will request all data, then slice it.The proxy make your data sliced while querying.
This is my filter that getting data from ElasticSearch.
class IndexView(AjaxListView):
template_name = '****'
context_object_name = '****'
page_template = '****'
def get_queryset(self):
params = {}
# get query params
for item in self.request.GET.items():
if item[0] == 'page' or item[0] == 'querystring_key':
continue
params[item[0]] = item[1]
# no filter
if len(params) == 0:
return ****.objects.filter().order_by('-date')
else:
return ESResult(params)
class ESResult(object):
def __init__(self, params):
self.params = params
def __len__(self):
s = self.search_es()
if s:
s = s[:1]
r = s.execute()
return r['hits']['total']
else:
return 0
def __getitem__(self, item):
assert isinstance(item, slice)
result = []
s = self.search_es()
if s:
s = s[item.start:item.stop] # slice before querying
r = s.execute()
for a in r.to_dict()['hits']['hits']:
one = a['_source']
one['id'] = int(a['_id'])
result.append(one)
return result
def search_es():
...
# filter request here
...
The list object used by AjaxListView is defined by get_queryset() method. To filter the queryset based on the users input, you may refer to POST method:
from app.forms import BlogFilterForm
class Blog(LoginRequiredMixin, AjaxListView):
context_object_name = "posts"
template_name = 'blog/blog.html'
page_template = 'blog/post_list.html'
success_url = '/blog'
def get_queryset(self): # define queryset
queryset = Post.objects.all() # default queryset
if self.request.method == 'POST': # check if the request method is POST
form = BlogFilterForm(self.request.POST) # define form
if form.is_valid():
name = form.cleaned_data['name'] # retrieve data from the form
if name:
queryset = queryset.filter(name=name) # filter queryset
else:
queryset = queryset
return queryset
def get_context_data(self, **kwargs):
context = super(Blog, self).get_context_data(**kwargs)
context['form'] = BlogFilterForm() # define context to render the form on GET method
return context
def post(self, request, *args, **kwargs): # define post method
return super(Blog, self).get(request, args, kwargs)
The endless pagination should work fine.
using filters with django-endless-pagination