Skip to content

Commit

Permalink
3.7 compat
Browse files Browse the repository at this point in the history
  • Loading branch information
aleontiev committed Aug 6, 2019
1 parent 2b7e85c commit 0d3fe79
Show file tree
Hide file tree
Showing 14 changed files with 48 additions and 212 deletions.
150 changes: 10 additions & 140 deletions dynamic_rest/compat.py
Original file line number Diff line number Diff line change
@@ -1,159 +1,29 @@
# flake8: noqa
from __future__ import absolute_import

from django.utils import six
from django import VERSION

DJANGO110 = VERSION >= (1, 10)
try:
from django.urls import (
NoReverseMatch,
RegexURLPattern,
RegexURLResolver,
ResolverMatch,
Resolver404,
get_script_prefix,
reverse,
reverse_lazy,
resolve
)
except ImportError:
from django.core.urlresolvers import ( # Will be removed in Django 2.0
NoReverseMatch,
RegexURLPattern,
RegexURLResolver,
ResolverMatch,
Resolver404,
get_script_prefix,
reverse,
reverse_lazy,
resolve
)


def set_many(instance, field, value):
if DJANGO110:
field = getattr(instance, field)
field.set(value)
else:
setattr(instance, field, value)


try:
from rest_framework.relations import Hyperlink
except ImportError:
class Hyperlink(six.text_type):
"""
A string like object that additionally has an associated name.
We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others.
Taken from DRF 3.2, used for compatability with DRF 3.1.
TODO(compat): remove when we drop compat for DRF 3.1.
"""
def __new__(self, url, obj):
ret = six.text_type.__new__(self, url)
ret.obj = obj
return ret

def __getnewargs__(self):
return(str(self), self.name,)

@property
def name(self):
# This ensures that we only called `__str__` lazily,
# as in some cases calling __str__ on a model instances *might*
# involve a database lookup.
return six.text_type(self.obj)

is_hyperlink = True

try:
from rest_framework.renderers import AdminRenderer
except ImportError:
from django.template import RequestContext, loader
from rest_framework.request import override_method
from rest_framework.renderers import BrowsableAPIRenderer

class AdminRenderer(BrowsableAPIRenderer):
template = 'rest_framework/admin.html'
format = 'admin'

def render(self, data, accepted_media_type=None, renderer_context=None):
self.accepted_media_type = accepted_media_type or ''
self.renderer_context = renderer_context or {}

response = renderer_context['response']
request = renderer_context['request']
view = self.renderer_context['view']

if response.status_code == 400:
# Errors still need to display the list or detail information.
# The only way we can get at that is to simulate a GET request.
self.error_form = self.get_rendered_html_form(data, view, request.method, request)
self.error_title = {'POST': 'Create', 'PUT': 'Edit'}.get(request.method, 'Errors')

with override_method(view, request, 'GET') as request:
response = view.get(request, *view.args, **view.kwargs)
data = response.data

template = loader.get_template(self.template)
context = self.get_context(data, accepted_media_type, renderer_context)
context = RequestContext(renderer_context['request'], context)
ret = template.render(context)

# Creation and deletion should use redirects in the admin style.
if (response.status_code == 201) and ('Location' in response):
response.status_code = 302
response['Location'] = request.build_absolute_uri()
ret = ''

if response.status_code == 204:
response.status_code = 302
try:
# Attempt to get the parent breadcrumb URL.
response['Location'] = self.get_breadcrumbs(request)[-2][1]
except KeyError:
# Otherwise reload current URL to get a 'Not Found' page.
response['Location'] = request.full_path
ret = ''

return ret

def get_context(self, data, accepted_media_type, renderer_context):
"""
Render the HTML for the browsable API representation.
"""
context = super(AdminRenderer, self).get_context(
data, accepted_media_type, renderer_context
)

paginator = getattr(context['view'], 'paginator', None)
if (paginator is not None and data is not None):
try:
results = paginator.get_results(data)
except KeyError:
results = data
else:
results = data

if results is None:
header = {}
style = 'detail'
elif isinstance(results, list):
header = results[0] if results else {}
style = 'list'
else:
header = results
style = 'detail'

columns = [key for key in header.keys() if key != 'url']
details = [key for key in header.keys() if key != 'url']

context['style'] = style
context['columns'] = columns
context['details'] = details
context['results'] = results
context['error_form'] = getattr(self, 'error_form', None)
context['error_title'] = getattr(self, 'error_title', None)
return context
def replace_methodname(format_string, methodname):
"""
Partially format a format_string, swapping out any
'{methodname}' or '{methodnamehyphen}' components.
"""
methodnamehyphen = methodname.replace('_', '-')
ret = format_string
ret = ret.replace('{methodname}', methodname)
ret = ret.replace('{methodnamehyphen}', methodnamehyphen)
return ret
28 changes: 10 additions & 18 deletions dynamic_rest/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from django.db import models

from dynamic_rest.related import RelatedObject
from dynamic_rest.compat import DJANGO110


class Meta(object):
Expand Down Expand Up @@ -110,24 +109,17 @@ def get_field(self, field_name):
meta = model._meta

try:
if DJANGO110:
field = meta.get_field(field_name)
else:
field = meta.get_field_by_name(field_name)[0]
field = meta.get_field(field_name)
except:
if DJANGO110:
related_objs = (
f for f in meta.get_fields()
if (f.one_to_many or f.one_to_one)
and f.auto_created and not f.concrete
)
related_m2m_objs = (
f for f in meta.get_fields(include_hidden=True)
if f.many_to_many and f.auto_created
)
else:
related_objs = meta.get_all_related_objects()
related_m2m_objs = meta.get_all_related_many_to_many_objects()
related_objs = (
f for f in meta.get_fields()
if (f.one_to_many or f.one_to_one)
and f.auto_created and not f.concrete
)
related_m2m_objs = (
f for f in meta.get_fields(include_hidden=True)
if f.many_to_many and f.auto_created
)

related_objects = {
o.get_accessor_name(): o
Expand Down
3 changes: 2 additions & 1 deletion dynamic_rest/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
ClassLookupDict
)
from django.utils.html import mark_safe
from dynamic_rest.compat import reverse, NoReverseMatch, AdminRenderer
from dynamic_rest.compat import reverse, NoReverseMatch
from rest_framework.renderers import AdminRenderer
from dynamic_rest.conf import settings
from dynamic_rest import fields

Expand Down
11 changes: 4 additions & 7 deletions dynamic_rest/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
from django.shortcuts import redirect
from rest_framework import views
from rest_framework.response import Response
from rest_framework.routers import DefaultRouter, Route, replace_methodname
from rest_framework.routers import DefaultRouter, Route

from dynamic_rest.meta import get_model_table
from dynamic_rest.conf import settings
from dynamic_rest.compat import (
get_script_prefix,
)

from django.core.urlresolvers import resolve
from dynamic_rest.compat import get_script_prefix, resolve, replace_methodname

resource_map = {}
resource_name_map = {}
Expand Down Expand Up @@ -343,6 +339,7 @@ class UserSerializer(..):
url=url,
mapping=mapping,
name=replace_methodname(route_name, field_name),
initkwargs={}
initkwargs={},
detail='post' in mapping
))
return routes
2 changes: 1 addition & 1 deletion dynamic_rest/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.db.models.fields.files import FieldFile
from django.utils.functional import cached_property
from rest_framework import exceptions, serializers
from rest_framework.fields import SkipField, JSONField, empty
from rest_framework.fields import SkipField, empty
from rest_framework.reverse import reverse
from rest_framework.exceptions import ValidationError
from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
Expand Down
6 changes: 5 additions & 1 deletion dynamic_rest/templatetags/dynamic_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from django.db.models.fields.files import FieldFile
from django.http.request import QueryDict
from dynamic_rest.version import version
from urlparse import urlparse, urlunparse
try:
from urlparse import urlparse, urlunparse
except ImportError:
from urllib.parse import urlparse, urlunparse

from uuid import UUID
from django import template
from django.utils.safestring import mark_safe
Expand Down
2 changes: 1 addition & 1 deletion dynamic_rest/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from rest_framework.fields import empty
from rest_framework.test import APIClient
from django.contrib.auth import get_user_model
from .compat import resolve
from dynamic_rest.compat import resolve
from dynamic_rest.meta import Meta


Expand Down
4 changes: 2 additions & 2 deletions dynamic_rest/ui.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from random import randint
import json

from django.utils.functional import cached_property
from django.utils import six
from decimal import Decimal
from django.template import loader
from rest_framework.compat import unicode_to_repr

from rest_framework.fields import SkipField
from dynamic_rest.utils import is_truthy
from dynamic_rest import fields as dfields

Expand Down Expand Up @@ -144,7 +144,7 @@ def __init__(self, name, fields, serializer, instance=None, main=False):
for field in fields:
try:
self.fields.append(serializer.get_field_value(field, instance))
except KeyError:
except (KeyError, SkipField):
pass
if len(self.fields) == 1:
self.field = self.fields[0]
Expand Down
4 changes: 2 additions & 2 deletions install_requires.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Django>=1.8,<1.12
djangorestframework>=3.1.0,<3.6.4
Django>=1.8,<2.1
djangorestframework>=3.7.0,<3.9.0
inflection==0.3.1
requests
arrow
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ dj-database-url==0.3.0
django-debug-toolbar==1.7
flake8==2.4.0
model_mommy
pytest==3.3.2
pytest-cov==2.5.1
pytest-django==3.2.1
pytest-sugar==0.9.1
pytest
pytest-cov
pytest-django
pytest-sugar
psycopg2>=2.7
tox-pyenv==1.0.2
tox==2.3.1
djay==0.0.4
djay==0.0.8
django-override-storage
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
NAME = 'dynamic-rest'
DESCRIPTION = 'Adds Dynamic API support to Django REST Framework.'
URL = 'http://github.com/AltSchool/dynamic-rest'
VERSION = '3.5.6'
VERSION = '3.5.7'
SCRIPTS = ['manage.py']

setup(
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_blueprints(self):
# generate an API endpoint for the generated model
application.execute('generate api v0 foo --not-interactive')
# start the server
server = application.execute('serve 9123', async=True)
server = application.execute('serve 9123', run_async=True)
time.sleep(2)

# verify a simple POST flow for the "foo" resource
Expand Down
30 changes: 1 addition & 29 deletions tests/test_viewsets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json


from django.test import TestCase
from django.test.client import RequestFactory
from rest_framework import exceptions, status
Expand All @@ -10,7 +11,6 @@
from tests.models import Dog, Group, User
from tests.setup import create_fixture
from tests.viewsets import (
GroupNoMergeDictViewSet,
UserViewSet
)

Expand Down Expand Up @@ -123,34 +123,6 @@ def test_is_null_casting(self):
self.assertEqual(out['_include']['f12__isnull'], None)


class TestMergeDictConvertsToDict(TestCase):

def setUp(self):
self.fixture = create_fixture()
self.view = GroupNoMergeDictViewSet.as_view({'post': 'create'})
self.rf = RequestFactory()

def test_merge_dict_request(self):
data = {
'name': 'miao',
'random_input': [1, 2, 3]
}
# Django test submits data as multipart-form by default,
# which results in request.data being a MergeDict.
# Wrote UserNoMergeDictViewSet to raise an exception (return 400)
# if request.data ends up as MergeDict, is not a dict, or
# is a dict of lists.
request = Request(self.rf.post('/groups/', data))
try:
response = self.view(request)
self.assertEqual(response.status_code, 201)
except NotImplementedError as e:
message = '{0}'.format(e)
if 'request.FILES' not in message:
self.fail('Unexpected error: %s' % message)
# otherwise, this is a known DRF 3.2 bug


class BulkUpdateTestCase(TestCase):

def setUp(self):
Expand Down
Loading

0 comments on commit 0d3fe79

Please sign in to comment.