Skip to content
This repository was archived by the owner on Jan 3, 2024. It is now read-only.

change folder placement of new rest framework and django filter #21

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -365,7 +365,7 @@ similarity = WordSimilarity(Value('Animal Farm'), F('title'))
Books.objects.annotate(similarity=similarity)
```

## tipsi_tools.django_filters.filters.NumberInFilter
## tipsi_tools.drf.filters.NumberInFilter

Django filter that match if integer is in the integers list separated by comma

@@ -419,13 +419,13 @@ def my_api(data):
print(f'Data: {data["test_int"]} and {data["test_str"]}')
```

## tipsi_tools.rest_framework.pagination.ApiPageNumberPagination
## tipsi_tools.drf.pagination.ApiPageNumberPagination

Allow turn off pagination by specifying zero page_zize.

```
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'tipsi_tools.rest_framework.pagination.ApiPageNumberPagination',
'DEFAULT_PAGINATION_CLASS': 'tipsi_tools.drf.pagination.ApiPageNumberPagination',
...
}
```
@@ -437,7 +437,7 @@ Pretty Django Rest Framework API renderer with error codes.
```
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'tipsi_tools.rest_framework.renderers.ApiRenderer',
'tipsi_tools.drf.renderers.ApiRenderer',
},
...
}
@@ -449,12 +449,12 @@ Pretty Django Rest Framework API exception handler with error codes.

```
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'tipsi_tools.rest_framework.handlers.api_exception_handler',
'EXCEPTION_HANDLER': 'tipsi_tools.drf.handlers.api_exception_handler',
...
}
```

## tipsi_tools.rest_framework.asserts.assert_validation_error
## tipsi_tools.drf.asserts.assert_validation_error

Helper assert function to be used in tests to match the validation error codes.

2 changes: 1 addition & 1 deletion tipsi_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = '1.47.0'
__version__ = '1.47.1'

import tipsi_tools.tipsi_logging as logging # noqa
16 changes: 16 additions & 0 deletions tipsi_tools/drf/asserts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def assert_validation_error(response, field, code):
assert 'error' in response
error = response['error']

assert 'detail' in error
detail = error['detail']

assert field in detail
items = detail[field]

found = False
for item in items:
if item['code'] == code:
found = True

assert found
29 changes: 28 additions & 1 deletion tipsi_tools/drf/filters.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,34 @@
from functools import reduce

import django_filters
from django.db.models import Q
from django.db.models import Q, F, Value
from django_filters import CharFilter
from django_filters.rest_framework import BaseInFilter, NumberFilter

from tipsi_tools.django.db.pgfields import WordSimilarity
from tipsi_tools.django.db.utils import set_word_similarity_threshold


class NumberInFilter(BaseInFilter, NumberFilter):
pass


class TrigramFilter(CharFilter):
def __init__(self, *args, **kwargs):
self.similarity_threshold = kwargs.pop('similarity_threshold', None)
super().__init__(*args, **kwargs)

def filter(self, qs, value):
if value:
if self.similarity_threshold:
set_word_similarity_threshold(1 - self.similarity_threshold)
similarity = WordSimilarity(Value(value), F(self.field_name))
qs = (
qs.annotate(similarity=similarity)
.order_by('similarity', 'pk')
.filter(**{f'{self.field_name}__similar': value})
)
return qs


class EnumFilter(django_filters.CharFilter):
37 changes: 37 additions & 0 deletions tipsi_tools/drf/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from django.http import Http404
from django.core.exceptions import PermissionDenied

from rest_framework import exceptions
from rest_framework.views import set_rollback
from rest_framework.response import Response


def api_exception_handler(exc, context):

if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()

if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait

error = {}
error['reason'] = exc.__class__.__name__
error['status'] = exc.status_code
error['code'] = exc.default_code
error['message'] = exc.default_detail
error['detail'] = exc.get_full_details()

set_rollback()
return Response(
{'error': error},
status=exc.status_code,
headers=headers,
)

return None
25 changes: 25 additions & 0 deletions tipsi_tools/drf/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import _positive_int


class ApiPageNumberPagination(PageNumberPagination):
''' Allow turn off pagination by specifying zero page_zize.'''

page_size_query_param = 'page_size'

def get_page_size(self, request):
"""
Disable pagination by 'listening' for a zero value for
page_size_query_param
"""
if self.page_size_query_param:
try:
return _positive_int(
request.query_params[self.page_size_query_param],
strict=False,
cutoff=self.max_page_size
)
except (KeyError, ValueError):
pass

return self.page_size
31 changes: 31 additions & 0 deletions tipsi_tools/drf/renderers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from rest_framework.renderers import JSONRenderer


class ApiRenderer(JSONRenderer):

def render(
self,
data,
media_type=None,
renderer_context=None,
):
wrapper = {
'version': '001',
}
# move error to the root level
if hasattr(data, 'get') and data.get('error'):
wrapper['error'] = data['error']
# UserWrapper support
wrapper['status_code'] = data['error']['status']
wrapper['error_message'] = data['error']['message']

del data['error']

if data is not None:
wrapper['data'] = data

return super().render(
wrapper,
media_type,
renderer_context,
)