Pass context to pagination class in Django Rest Framework - python

I have my custom pagination class.
class BasicPagination(PageNumberPagination):
page_size = 3
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data):
has_next, has_previous = False, False
if self.get_next_link():
has_next = True
if self.get_previous_link():
has_previous = True
meta = collections.OrderedDict([
('page', self.page.number),
('has_next', has_next),
('has_previous', has_previous),
])
ret = collections.OrderedDict(meta=meta)
ret["results"] = data
return Response(ret)
Also I have a generics.ListCreateAPIView class, which has custom queryset method and pagination_class = BasicPagination. I wanna pass self.kwargs.get("obj_type") to pagination class so that it displays obj_type not results. Here is my class view. How can I pass self.kwargs to pagination class?
class Translation(ListCreateAPIView):
pagination_class = BasicPagination
serializer_class = TranslationStepSerializer
def get_queryset(self):
api_controller = ApiController.load()
obj_type = self.kwargs.get("obj_type")
pk = self.kwargs.get("pk")
data = api_controller.get_translation(obj_type, pk)
return data if not None else None

I am assuming that by this -
it displays obj_type not results
you mean that you want the key in your response to be obj_type instead of "results". That is obj_type is a string in your code.
I had a similar requirement where I wanted to modify the response based on certain conditions. As a workaround, I added all the required parameters to data itself, through which I customised the paginated response.
def get_paginated_response(self, data):
if self.get_next_link():
next_page = data["page_no"] + 1
else:
next_page = 0
response = {
"next": next_page,
'count': self.page.paginator.count,
'cards': data["cards"],
'companies': data["companies"],
'positions': data["positions"],
'cities':data["cities"]
}
tags = data.get('tags', None)
if tags is not None:
response['tags'] = tags
return Response(data=response)
In your case you can do something like:
ret[data['obj_type']] = data['results']
And prior to this, in the queryset:
data = {'results': api_controller.get_translation(obj_type, pk), 'obj_type': obj_type}

It's late I know. But in your view-set you can override get_paginated_response and list methods:
class Translation(ListCreateAPIView):
pagination_class = BasicPagination
serializer_class = TranslationStepSerializer
def get_queryset(self):
api_controller = ApiController.load()
obj_type = self.kwargs.get("obj_type")
pk = self.kwargs.get("pk")
data = api_controller.get_translation(obj_type, pk)
return data if not None else None
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data, extra_keys={'obj_type': self.kwargs.get("obj_type")})
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_paginated_response(self, data, extra_keys):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data, extra_keys)
and in your pagination class you can get extra_keys as argument:
class BasicPagination(PageNumberPagination):
page_size = 3
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data, extra_keys):
# here you can get your passed keys
# for example for obj_type
obj_type = extra_keys['obj_type']
has_next, has_previous = False, False
if self.get_next_link():
has_next = True
if self.get_previous_link():
has_previous = True
meta = collections.OrderedDict([
('page', self.page.number),
('has_next', has_next),
('has_previous', has_previous),
])
ret = collections.OrderedDict(meta=meta)
ret["results"] = data
return Response(ret)
Note: I used dictinary to pass your variable because if there be another variables to pass this is neater. but you can simply just pass your desired variable.

Related

How to create a service from a function?

I want to create a service using Django Rest API. I have a function. The result of this function should return 2 values and I should return these values in JSON API format.
The function will work like this. I will receive the features_list as a parameter and I will use it to create a result and display it as a service in json format in def prediction function.
I created a sample API (I guess) it is class PredictionSet in my views but I actually want to make service the def prediction function in my views.
I cannot understand how to apply it. I am so confused. Any help would be appreciated.
models.py
class Liquidity(models.Model):
pred_y = models.CharField(max_length=600)
score = models.FloatField()
views.py
class PredictionSet(viewsets.ModelViewSet):
queryset = Liquidity.objects.all()
serializer_class = LiquiditySerializer
def prediction(request, features_list):
filename = config.FINAL_MODEL_PATH
classifier = pickle.load(open(filename, 'rb'))
scale_file = config.SCALER_PATH
scaler = pickle.load(open(scale_file, 'rb'))
sample = np.array(features_list).reshape(1, -1)
sample_scaled = scaler.transform(sample)
pred_y = classifier.predict(sample_scaled)
prob_y = classifier.predict_proba(sample_scaled)
if prob_y[0][1] < 0.5:
score = 0
elif prob_y[0][1] <= 0.69:
score = 1
else:
score = 2
pred_y = pred_y[0]
prediction_obj = Liquidity.objects.get_or_create(pred_y=pred_y, score=score)
prediction_result = prediction_obj.pred_y
prediction_score = prediction_obj.score
context = {
'prediction_result ': prediction_result,
'prediction_score ': prediction_score,
}
return context
serializer.py
class LiquiditySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Liquidity
fields = '__all__'
If you want to return custom JSON from a ModelViewset in DRF, you can override .list() and/or .retrieve() like this :
from rest_framework import status
from rest_framework.response import Response
class PredictionSet(viewsets.ModelViewSet):
queryset = Liquidity.objects.all()
serializer_class = LiquiditySerializer
# Your custom function definition
def prediction(self, request, features_list):
# The content
def retrieve(self, request, *args, **kwargs):
result = prediction(...) # Call your custom service and got result
# Return the result as JSON (url = /api/v1/predictions/1) an object
return Response({'data': result}, status=status.HTTP_200_OK)
def list(self, request, *args, **kwargs):
result = prediction(...) # Call your custom service and got result
# Return the result as JSON (url = /api/v1/predictions) a list of objects
return Response({'data': result}, status=status.HTTP_200_OK)
For more details follow this link

Django Pagination: switch between paginated/non-paginated ListView

I'm trying to elaborate a smart way to switch between a paginated template and a non-paginated one.
I already have a working paginator and I was thinking of adding a button next to it that read "Show all results" that linked to a non-paginated list, from there then there would be another button to go back to the paginated list.
1) Easy Solution
Use 2 ListViews with different assignations of the attribute paginate_by (django default to set pagination), but since I have many lists in my project it wouldn't be convenient (not much smart either).
2) Solution I'm stuck on
Write a Mixin (that will later be extended by my ListViews) to set the variable paginate_by based on a condition and then add some useful variables to the context :
class PaginationMixin:
no_pagination = False
no_pagination_url = ''
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
return self.no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
#...
def get_no_pagination_url(self):
return reverse('mylist_urlname')
PROBLEM: I don't know how to set the no_pagination variable from the template. Is there some way to do this?
Thanks for the help.
UPDATED SOLUTION (edited from #hi-lan solution):
This way it will show all results and also keep urlparams (from filters or other) if present.
class PaginationMixin:
toggle_pagination = False
toggle_pagination_url = ''
no_pagination = False
view_name = ''
urlparams_dict = {}
def get(self, request, page=None, *args, **kwargs):
#store current GET params and pop 'page' key
self.urlparams_dict = request.GET
self.urlparams_dict.pop('page', None)
page = page or request.GET.get('page', '1')
if page == 'all':
page = self.paginate_by = None
self.no_pagination = True
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_toggle_pagination_url(self):
# variables to set in view to toggle this mixin
if self.toggle_pagination and self.view_name:
if not self.no_pagination:
extra = {'page': 'all'}
self.urlparams_dict.update(extra)
else:
self.urlparams_dict.pop('page', None)
# url keeps track of urlparams adds page=all if toggled
self.toggle_pagination_url = reverse(self.view_name) + '?' + urlencode(self.urlparams_dict)
return self.toggle_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['toggle_pagination_url'] = self.get_toggle_pagination_url()
context['toggle_pagination'] = self.toggle_pagination
return context
The problem is at data flow back from user to indicate non pagination. The only way I can think of is to use special page number. There are two options, depends on which way you config urls.py.
In case of path('objects/page<int:page>/',
PaginatedView.as_view()), special number is 0 (as normal page number
is started from 1).
In case of /objects/?page=3, special number can be all.
In either case, we need to override get method as it is where we can retrieve user's selection.
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
# For using path
extra = {'page': '0'}
no_pagination_url = reverse(self.view_name, kwargs=extra)
# For using query params
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
view_name = 'mylist_urlname'
#...
I'm trying the UPDATED SOLUTION of gccallie with this view :
class StageTempList(PaginationMixin, LoginRequiredMixin, SingleTableMixin, FilterView):
view_name = 'stagetemp-list'
table_class = StageTempTable
model = StageTemp
filterset_class = StageTempFilter
template_name = 'stage/stagetemp_list.html'
paginate_by = 30
strict = False
But when get_paginate_by return None, I get 25 rows.
Django version 2.1.2
UPDATE : the PaginationMixin Class I use
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
self.no_pagination = True
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context

How can I filter the physicalserver if its id is in the given param?

How can I filter the physicalserver if its id is in the given param?
class PhysicalServerListAPIView(ListAPIView):
serializer_class = PhysicalServerListSerializer
permission_classes = [IsSuperAdminOrObjEqualsSelf]
pagination_class = CommonPagination
def get_queryset(self):
query_params = self.request.query_params
qs = PhysicalServer.objects.filter(user=self.request.user)
id_list = get_param_from_query_params(query_params, 'id_list')
if id_list:
qs = qs.filter(Q(id__in=id_list)) # I want to filter if the physicalserver id is in the `id_list`
return qs
the code of the method:
def get_param_from_query_params(query_params, param):
"""
:param query_params:
:param param:
:return:
"""
param_temp = None
try:
mutable = query_params._mutable
query_params._mutable = True
param_list = query_params.pop(param)
param_temp = param_list[0] if (isinstance(param_list, list) and len(param_list) > 0) else ''
query_params._mutable = mutable
except Exception as e:
pass
return param_temp
How can I filter the physicalserver if its id is in the given param?
EDIT-1
My id_list in the url form is:
id_list[]=24&id_list[]=25&id_list[]=27
The get id_list method should be:
id_list = query_params.getlist('id_list')
This is the answer.

Test POST method to Django REST viewsets

Docs says only mapping of GET
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
tests.py:
def test_admin_can_create_role(userprofiles, aoo_admin, bug_manager, note_admin):
aoo = User.objects.get(username='aoo')
factory = APIRequestFactory()
view = RoleViewSet.as_view()
url = reverse('api:role-list')
data = {
'name': 'FirstAdmin',
'type': Role.RoleType.admin,
'company': 1,
}
request = factory.post(url, data=data, format='json')
force_authenticate(request, user=aoo)
response = view(request)
assert 201 == response.data
viewsets.py
class RoleViewSetPermission(permissions.BasePermission):
message = 'Only Manager or Admin are allowed'
def has_permission(self, request, view):
user = request.user
return user.has_perm('roles.add_role') \
and user.has_perm('roles.change_role') \
and user.has_perm('roles.delete_role')
class RoleViewSet(viewsets.ModelViewSet):
permission_classes = (RoleViewSetPermission,)
queryset = Role.objects.all()
serializer_class = RoleSerializer
filter_backends = (filters.DjangoFilterBackend, SearchFilter)
filter_class = RoleFilter
search_fields = ('name', 'description', 'user__username', 'company__name', 'company__name_th')
def filter_queryset(self, queryset):
try:
company = self.request.user.user_profile.companyappid.company
except AttributeError:
logger.error(f'{self.request.user} has AttributeError')
return Role.objects.none()
else:
logger.info(f'{self.request.user} is {company} member')
return queryset.filter(company=company)
Trackback:
cls = <class 'poinkbackend.apps.roles.api.viewsets.RoleViewSet'>, actions = None, initkwargs = {}
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# The suffix initkwarg is reserved for identifying the viewset type
# eg. 'List' or 'Instance'.
cls.suffix = None
# actions must not be empty
if not actions:
> raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
E TypeError: The `actions` argument must be provided when calling `.as_view()` on a ViewSet. For example `.as_view({'get': 'list'})`
../../.pyenv/versions/3.6.3/envs/poink/lib/python3.6/site-packages/rest_framework/viewsets.py:55: TypeError
Question:
How to do force_authenticate and request.post to the viewsets?
I have no problem with get. It has an answer already in the SO
References:
http://www.django-rest-framework.org/api-guide/viewsets/
I have to use APIClient not APIRequestFactory.
I though it has only one way to do testing.
Here is my example.
def test_admin_can_create_role(userprofiles, aoo_admin, bug_manager, note_admin):
aoo = User.objects.get(username='aoo')
client = APIClient()
client.force_authenticate(user=aoo)
url = reverse('api:role-list')
singh = Company.objects.get(name='Singh')
data = {
'name': 'HairCut',
'type': Role.RoleType.admin,
'company': singh.id, # Must be his companyid. Reason is in the RoleSerializer docstring
}
response = client.post(url, data, format='json')
assert 201 == response.status_code

Django success url using kwargs

I am trying to amend my get_success_url so that if any kwargs have been passed to it, I can build the returned url using them.
Heres what I have so far:
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
self.get_success_url(idnumber = model_b.pk)
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
self.archive_calc(object_id)
#save
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
So far this just gives a keyerror detailing 'idnumber'.
I have printed kwargs['idnumber'] and it returns the pk as expected however I just cant seem to see where I am going wrong with this.
Thanks in advance.
form_valid should return a HttpResponseRedirect https://github.com/django/django/blob/master/django/views/generic/edit.py#L57 which in your case, you never do. I dont know if you have any code after #save, but take a look at the comments I made in your code
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
return self.get_success_url(idnumber = model_b.pk) # you never return this value
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
return HttpResponseRedirect(self.archive_calc(object_id)) # you never return a `HttpResponse`
#save -- If this is where you are saving... you can store the value from archive and return it after saving
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
Also you don't need to manually copy the fields, just do (assuming there are no unique constraints because if there were, your version would fail too):
def archive_calc(self, object_id):
c = self.model.objects.get(id = object_id)
c.pk = None
c.save()
return self.get_success_url(idnumber = c.pk)
After playing around with #Ngenator's answer and various other posts on here I have the following working code. However its not very nice to look at :(
def get_success_url(self):
if self.pknumber != None:
return reverse_lazy('pstdetail', args = (self.pknumber,))
else:
return reverse_lazy('pstdetail', args = (self.object.id,))
I have this self.pknumber = model_b.pk in the necessary place within the view and self.pknumber = None else where to enable the if statement to build the required url. Hope this helps anyone and feel free to point out any errors/improvements.
https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/UpdateView/
You cannot pass parameters in get_success_url(self) method. You can only refer to self parameter. For example self.kwargs['pk'].

Categories

Resources