How to use Django Rest Framework + Datatables + Class Based Views?
This is a python snipped to use Django Rest Framework + Datatables + Class Based Views.
import re
from rest_framework.generics import GenericAPIView
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.response import Response
class DataTablePagination(LimitOffsetPagination): # pragma: no cover
"""Custom pagination class for datatable server side processing."""
limit_query_param = "length"
offset_query_param = "start"
def get_paginated_response(self, data):
"""See datatables server side processing."""
return Response({
"recordsTotal": self.count,
"recordsFiltered": self.count,
"data": data,
"draw": int(self.request.GET["draw"])
})
#staticmethod
def get_datatable_ordering(request):
"""Parse datatables conf parameters."""
ordering = []
columns = dict()
for key in request.GET:
if key.startswith('columns'):
col = dict()
keys = [i[1:-1] for i in re.findall(r'\[\w+\]', key)]
for i in range(len(keys) - 1):
if i == 0:
if keys[i] not in columns: columns[keys[i]] = dict()
col = columns[keys[i]]
else:
if keys[i] not in col: col[keys[i]] = dict()
col = col[keys[i]]
col[keys[-1]] = request.GET[key]
if 'order[0][column]' in request.GET:
ret = columns[request.GET['order[0][column]']]['data']
if request.GET['order[0][dir]'] != 'asc': ret = '-' + ret
ordering.append(ret.replace('.', '__'))
return ordering
class DatatablesView(GenericAPIView): # pragma: no cover
"""Base view with datatables server side integration."""
ordering = ("id",)
def initialize_request(self, request, *args, **kwargs):
"""Enable server side processing for datatables and `q` searching."""
request.GET = request.GET.copy()
if 'draw' in request.GET:
self.pagination_class = DataTablePagination
self.ordering = self.pagination_class.get_datatable_ordering(
request) or self.ordering
if 'length' in request.GET and str(request.GET['length']) == '-1':
request.GET['length'] = 1e9
if 'search[value]' in request.GET:
request.GET['search'] = request.GET['search[value]']
elif 'q' in request.GET and 'search' not in request.GET:
request.GET['search'] = request.GET['q']
return super().initialize_request(request, *args, **kwargs)
Related
I need to override user save method in my custom OAuth2Adapter. How i can do this?
I need save some special fields in my user Model
auth_provider/views.py
import requests
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
from config.settings.base import EXAMPLE_URLS
from app.users.auth_provider.provider import ExampleProvider
class ExampleAdapter(OAuth2Adapter):
provider_id = ExampleProvider.id
access_token_url = EXAMPLE_URLS["web"] + "oauth/token"
authorize_url = EXAMPLE_URLS["web"] + "oauth/authorize"
profile_url = EXAMPLE_URLS["api"] + "me"
def complete_login(self, request, app, access_token, **kwargs):
headers = {"Authorization": "Bearer %s" % access_token}
extra_data = requests.get(self.profile_url, headers=headers)
return self.get_provider().sociallogin_from_response(request, extra_data.json())
oauth2_login = OAuth2LoginView.adapter_view(ExampleAdapter)
oauth2_callback = OAuth2CallbackView.adapter_view(ExampleAdapter)
I have created a class for retrieving data about a specific food product from farsecret API.
In class I have created 3 functions for:
*obtaining authorization
*getting id of item, for which we are looking
*download data of item
class IngredientImport(APIView):
def get_authorization_token(self):
request_token_url = "https://oauth.fatsecret.com/connect/token"
consumer_key = os.environ.get('NTS_Client_ID')
consumer_secret = os.environ.get('NTS_Client_Secret')
data = {'grant_type':'client_credentials', "scope":"basic"}
access_token_response = requests.post(
request_token_url,
data=data,
verify=False,
allow_redirects=False,
auth=(consumer_key, consumer_secret)
)
return access_token_response.json()["access_token"]
def get_list_by_name(self, name, access_token):
api_url = "https://platform.fatsecret.com/rest/server.api"
params={
"method":"foods.search",
"search_expression":name,
"page_number":1,
"max_results":1,
"format":"json"
}
header = {"Authorization":access_token}
api_call_headers = {"Authorization": "Bearer " + access_token}
response = requests.get(
api_url,
params=params,
headers=api_call_headers
)
items = response.json()["foods"]
try:
return response.json()["foods"]["food"]["food_id"]
except KeyError:
return None
def import_item(self, item_id, api_token):
if item_id == None:
return None
api_url = "https://platform.fatsecret.com/rest/server.api"
params={"method":"food.get", "food_id":item_id, "format":"json"}
api_call_headers = {"Authorization": "Bearer " + access_token}
response = requests.get(
api_url,
params=params,
headers=api_call_headers
)
item = response.json()["food"]["servings"]["serving"]
item_name = response.json()["food"]["food_name"]
if type(item) == list:
item = item[0]
try:
portion_size = float(item["metric_serving_amount"])
carbs = round(float(item["carbohydrate"]) / portion_size * 100, 2)
fats = round(float(item["fat"]) / portion_size * 100, 2)
proteins = round(float(item["protein"]) / portion_size * 100, 2)
except KeyError:
return None
How can I implement this class in my aplication to avoid creating 3 different paths in urls.py for each function. Is it possible or should I break it into function-based views?
class IngredientImport(APIView):
def get_authorization_token(self):
...
def get_list_by_name(self, name, access_token):
...
def import_item(self, item_id, api_token):
...
def get(self, request):
# get name from query param
name = self.request.GET.get('name')
token = self.get_authorization_token()
food_list = self.get_list_by_name(name, token)
for food_id in food_list:
self.import_item(food_id, token)
return Response({'imported_foods': food_list})
Then, on your urls.py:
urlpatterns = [
path('import_foods', IngredientImport.as_view())
]
I using CursorPagination of Django Rest Framework, I want to override default page_size(10) and ordering('timestamp') in a single viewset. How can I do this?
I tried with my viewset but it's not success:
from rest_framework.pagination import CursorPagination
class ListAPIView(ListAPIView):
queryset = Cake.objects.all()
permission_classes = [AllowAny]
serializer_class = ListSerializer
pagination_class = CursorPagination
filter_backends = (OrderingFilter, DjangoFilterBackend)
filter_class = CakeListFilter
filterset_fields = ('cake_type', 'user__username')
ordering = '-date'
page_size = 5
You may create a new class inheriting from CursorPagination class in order to set custom page_size and/or max_page_size like so:
class CustomPageSizeCursorPagination(CursorPagination):
page_size = 5
max_page_size = 100
And then use this class as the pagination_class field of your viewset
WARNING: The following code is untested
Another option is to write a custom paginator class that gets the page size from the viewset. For example:
class PageSizeInViewSetCursorPagination(CursorPagination):
def get_page_size(self, request, viewset_page_size):
if self.page_size_query_param:
try:
return _positive_int(
request.query_params[self.page_size_query_param],
strict=True,
cutoff=self.max_page_size
)
except (KeyError, ValueError):
pass
return viewset_page_size or self.page_size
def paginate_queryset(self, queryset, request, view=None):
# Get the page_size from the viewset and then decide which page_size to use
viewset_page_size = getattr(view, 'page_size', None)
page_size = self.get_page_size(request, viewset_page_size)
# What follows is copy/paste of the code from CursorPagination paginate_queryset method
if not self.page_size:
return None
self.base_url = request.build_absolute_uri()
self.ordering = self.get_ordering(request, queryset, view)
self.cursor = self.decode_cursor(request)
if self.cursor is None:
(offset, reverse, current_position) = (0, False, None)
else:
(offset, reverse, current_position) = self.cursor
# Cursor pagination always enforces an ordering.
if reverse:
queryset = queryset.order_by(*_reverse_ordering(self.ordering))
else:
queryset = queryset.order_by(*self.ordering)
# If we have a cursor with a fixed position then filter by that.
if current_position is not None:
order = self.ordering[0]
is_reversed = order.startswith('-')
order_attr = order.lstrip('-')
# Test for: (cursor reversed) XOR (queryset reversed)
if self.cursor.reverse != is_reversed:
kwargs = {order_attr + '__lt': current_position}
else:
kwargs = {order_attr + '__gt': current_position}
queryset = queryset.filter(**kwargs)
# If we have an offset cursor then offset the entire page by that amount.
# We also always fetch an extra item in order to determine if there is a
# page following on from this one.
results = list(queryset[offset:offset + self.page_size + 1])
self.page = list(results[:self.page_size])
# Determine the position of the final item following the page.
if len(results) > len(self.page):
has_following_position = True
following_position = self._get_position_from_instance(results[-1], self.ordering)
else:
has_following_position = False
following_position = None
# If we have a reverse queryset, then the query ordering was in reverse
# so we need to reverse the items again before returning them to the user.
if reverse:
self.page = list(reversed(self.page))
if reverse:
# Determine next and previous positions for reverse cursors.
self.has_next = (current_position is not None) or (offset > 0)
self.has_previous = has_following_position
if self.has_next:
self.next_position = current_position
if self.has_previous:
self.previous_position = following_position
else:
# Determine next and previous positions for forward cursors.
self.has_next = has_following_position
self.has_previous = (current_position is not None) or (offset > 0)
if self.has_next:
self.next_position = following_position
if self.has_previous:
self.previous_position = current_position
# Display page controls in the browsable API if there is more
# than one page.
if (self.has_previous or self.has_next) and self.template is not None:
self.display_page_controls = True
return self.page
Note that in the above example the page_size from the request always takes precedence over whatever you have set up in your code. Then the viewset_page_size is the second in line and lastly the deafult page_size from the Pagination class.
Here is a custom pagination class that extends CursorPagination. It checks for ordering and page_size attributes defined in the viewset and if they exist use them. If not, fallback to original settings defined in the pagination class itself.
class NewsCursorPaginator(CursorPagination):
ordering = 'title'
page_size = 5
# get_page_size do not have view attribute, so we have our custom one
def get_custom_page_size(self, request, view):
viewset_page_size = getattr(view, 'page_size', None)
if viewset_page_size:
self.page_size = viewset_page_size
return super(NewsCursorPaginator, self).get_page_size(request)
def get_ordering(self, request, queryset, view):
viewset_ordering = getattr(view, 'ordering', None)
if viewset_ordering:
self.ordering = viewset_ordering
return super(NewsCursorPaginator, self).get_ordering(request, queryset, view)
def paginate_queryset(self, queryset, request, view=None):
self.page_size = self.get_custom_page_size(request, view)
return super(NewsCursorPaginator, self).paginate_queryset(queryset, request, view)
This implementation takes "limit" (page_size) as an optional querystring parameter.
class CursorPagination(pagination.CursorPagination):
page_size = settings.REST_FRAMEWORK["PAGE_SIZE"]
def get_custom_page_size(self, request, view):
try:
self.page_size = int(request.GET.get("limit"))
except (ValueError, TypeError):
pass
return super().get_page_size(request)
def paginate_queryset(self, queryset, request, view=None):
self.page_size = self.get_custom_page_size(request, view)
return super().paginate_queryset(queryset, request, view)
I want to make some tournament matches in a DetailView. But I can't figure out how to make a query of all registrations context['regs'] and use the queryset in my function create_matches().
class KategorieDetail(DetailView):
model = Kategorie
context_object_name = 'kategorie'
def create_matches(regs):
red_corner = []
blue_corner = []
matches = []
# Separate the regs into both corners
i = 1
for reg in regs:
if i%2 == 1:
red_corner.append(reg)
else:
blue_corner.append(reg)
i += 1
# Create Match-Ups
while blue_corner:
match = {'red': red_corner.pop(), 'blue': blue_corner.pop()}
matches.append(match)
return matches
def get_context_data(self, **kwargs):
context = super(KategorieDetail, self).get_context_data(**kwargs)
kid = context['kategorie'].id
context['regs'] = Registrierung.objects.filter(kategorie=context['kategorie'].id)
context['regs_count'] = context['regs'].count()
context['matches'] = create_matches(context['regs'].values(), kid)
return context
In my HTML-View I can't display the matches. If I say {{matches}}, I get:
HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/events/"
I also don't get why I have to give the Kategorie_ID to the create_matches(regs) function.
I try to use flask login in may app:
My controller:
#app.route("/process_log", methods=['POST'])
def process_login():
filled_form = LoginForm(request.form)
if filled_form.validate():
phone = filled_form.phone.data
password = filled_form.password.data
if User.phone_exists(phone) and User.pass_match(phone, password):
user = User.get_by_phone(phone)
login_user(user.get_id)
return redirect(url_for("index"))
else:
return render_template("login.html", form = filled_form, error = u"Не верный логин или пароль")
else:
return render_template("home.html", form = filled_form)
and I have some class with defined functions which required for API of flask login
My User class:
from pymongo import MongoClient
from bson.objectid import ObjectId
class User():
client = MongoClient()
db = client['test']
col = db['user']
user_id = None
def __init__(self, dic):
self.dic = dic
def is_authenticated():
return True
def is_anonymous():
return False
def is_active():
return True
def get_id(self):
return unicode(str(self.user_id))
def save(self):
self.user_id = self.col.insert(self.dic)
print "Debug:" + str(self.user_id)
#staticmethod
def _get_col():
client = MongoClient()
db = client['test']
col = db['user']
return col
#staticmethod
def phone_exists(phone):
col = User._get_col()
if col.find_one({'phone': phone}) != None:
return True
else:
return False
#staticmethod
def pass_match(phone, password):
col = User._get_col()
if col.find_one({'phone': phone})['password'] == password:
return True
else:
return False
#staticmethod
def get(userid):
col = User._get_col()
return col.find_one({'_id':userid})
#staticmethod
def get_by_phone(phone):
col = User._get_col()
dic = col.find_one({'phone': phone})
print dic['password']
return User(dic)
As you see function is_active is defined(Note:I also tried to pass refference with self)
But I still have this error AttributeError: 'function' object has no attribute 'is_active'
I am sorry for too much code here, but it should be pretty straightforward.
Note: I am using mongodb for my project.
Please help me to find my error. Thank you too much
One more thing:
Should I provide login_user(....) with Id or with my user object?
You must sent to login_user User instance (not id), see: https://github.com/maxcountryman/flask-login/blob/master/flask_login.py#L576.
So next code must work:
user = User.get_by_phone(phone)
login_user(user)