From e46d0f15e6dc399a1a391143f317a0c66e510b72 Mon Sep 17 00:00:00 2001 From: aleontiev Date: Tue, 28 Feb 2017 14:55:55 -0800 Subject: [PATCH 001/171] WIP --- dynamic_rest/links.py | 18 +- dynamic_rest/renderers.py | 55 +++- .../templates/dynamic_rest/admin.html | 253 ++++++++++++++++++ dynamic_rest/viewsets.py | 16 +- install_requires.txt | 2 +- tests/serializers.py | 3 + tests/settings.py | 3 +- 7 files changed, 336 insertions(+), 14 deletions(-) create mode 100644 dynamic_rest/templates/dynamic_rest/admin.html diff --git a/dynamic_rest/links.py b/dynamic_rest/links.py index c0743e8b..49c9d117 100644 --- a/dynamic_rest/links.py +++ b/dynamic_rest/links.py @@ -20,6 +20,16 @@ def merge_link_object(serializer, data, instance): # This generally only affectes Ephemeral Objects. return data + base_url = '' + if settings.ENABLE_HOST_RELATIVE_LINKS: + # if the resource isn't registered, this will default back to + # using resource-relative urls for links. + base_url = DynamicRouter.get_canonical_path( + serializer.get_resource_key(), + instance.pk + ) or '' + link_object['self'] = base_url + link_fields = serializer.get_link_fields() for name, field in six.iteritems(link_fields): # For included fields, omit link if there's no data. @@ -28,14 +38,6 @@ def merge_link_object(serializer, data, instance): link = getattr(field, 'link', None) if link is None: - base_url = '' - if settings.ENABLE_HOST_RELATIVE_LINKS: - # if the resource isn't registered, this will default back to - # using resource-relative urls for links. - base_url = DynamicRouter.get_canonical_path( - serializer.get_resource_key(), - instance.pk - ) or '' link = '%s%s/' % (base_url, name) # Default to DREST-generated relation endpoints. elif callable(link): diff --git a/dynamic_rest/renderers.py b/dynamic_rest/renderers.py index ff89c28a..47fc975c 100644 --- a/dynamic_rest/renderers.py +++ b/dynamic_rest/renderers.py @@ -1,5 +1,9 @@ """This module contains custom renderer classes.""" -from rest_framework.renderers import BrowsableAPIRenderer +from rest_framework.renderers import ( + BrowsableAPIRenderer, + AdminRenderer +) +from dynamic_rest.utils import unpack class DynamicBrowsableAPIRenderer(BrowsableAPIRenderer): @@ -18,3 +22,52 @@ def get_context(self, data, media_type, context): request = context['request'] context['directory'] = get_directory(request) return context + + +class DynamicAdminRenderer(AdminRenderer): + """Admin renderer override.""" + + COLUMN_BLACKLIST = ('id', 'links', 'url') + template = 'dynamic_rest/admin.html' + + def get_context(self, data, media_type, context): + def add_url(result): + if result.get('links', {}).get('self'): + result['url'] = result['links']['self'] + + context = super(DynamicAdminRenderer, self).get_context( + data, + media_type, + context + ) + + # to account for the DREST envelope + # (data is stored one level deeper than expected in the response) + results = unpack(context['results']) + if results is None: + style = 'detail' + header = {} + elif isinstance(results, list): + for result in results: + add_url(result) + header = results[0] if results else {} + style = 'list' + else: + add_url(results) + header = results + style = 'detail' + + columns = [ + key for key in header.keys() if key not in self.COLUMN_BLACKLIST + ] + context['results'] = results + context['columns'] = columns + context['details'] = columns + context['style'] = style + return context + + def get_raw_data_form(self, data, view, method, request): + print unpack(data) + return super( + DynamicAdminRenderer, self + ).get_raw_data_form(unpack(data), view, method, request) diff --git a/dynamic_rest/templates/dynamic_rest/admin.html b/dynamic_rest/templates/dynamic_rest/admin.html new file mode 100644 index 00000000..de011cd0 --- /dev/null +++ b/dynamic_rest/templates/dynamic_rest/admin.html @@ -0,0 +1,253 @@ +{% load staticfiles %} +{% load i18n %} +{% load rest_framework %} + + + + + {% block head %} + + {% block meta %} + + + {% endblock %} + + {% block title %}Django REST framework{% endblock %} + + {% block style %} + {% block bootstrap_theme %} + + + {% endblock %} + + + {% endblock %} + + {% endblock %} + + + {% block body %} + +
+ {% block navbar %} + + {% endblock %} + +
+ {% block breadcrumbs %} + + {% endblock %} + + +
+ {% if 'GET' in allowed_methods %} +
+
+
+ + +
+
+
+ {% endif %} + + {% if post_form %} + + {% endif %} + + {% if put_form %} + + {% endif %} + + {% if delete_form %} +
+ +
+ {% endif %} + + {% if filter_form %} + + {% endif %} + +
+ + +
+ {% block description %} + {{ description }} + {% endblock %} +
+ + {% if paginator %} + + {% endif %} + +
+ {% if style == 'list' %} + {% include "rest_framework/admin/list.html" %} + {% else %} + {% include "rest_framework/admin/detail.html" %} + {% endif %} +
+ + {% if paginator %} + + {% endif %} +
+
+ +
+
+ + + + + + + + {% if error_form %} + + + {% endif %} + + {% if filter_form %} + {{ filter_form }} + {% endif %} + + {% block script %} + + + + + + + + + {% endblock %} + + {% endblock %} + diff --git a/dynamic_rest/viewsets.py b/dynamic_rest/viewsets.py index 3a85f097..2dfe77f2 100644 --- a/dynamic_rest/viewsets.py +++ b/dynamic_rest/viewsets.py @@ -4,7 +4,10 @@ from django.utils import six from rest_framework import exceptions, status, viewsets from rest_framework.exceptions import ValidationError -from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer +from rest_framework.renderers import ( + BrowsableAPIRenderer, + JSONRenderer, +) from rest_framework.response import Response from dynamic_rest.conf import settings @@ -12,7 +15,10 @@ from dynamic_rest.metadata import DynamicMetadata from dynamic_rest.pagination import DynamicPageNumberPagination from dynamic_rest.processors import SideloadingProcessor -from dynamic_rest.renderers import DynamicBrowsableAPIRenderer +from dynamic_rest.renderers import ( + DynamicBrowsableAPIRenderer, + DynamicAdminRenderer +) from dynamic_rest.utils import is_truthy UPDATE_REQUEST_METHODS = ('PUT', 'PATCH', 'POST') @@ -75,7 +81,11 @@ class WithDynamicViewSetMixin(object): # TODO: add support for `sort{}` pagination_class = DynamicPageNumberPagination metadata_class = DynamicMetadata - renderer_classes = (JSONRenderer, DynamicBrowsableAPIRenderer) + renderer_classes = ( + JSONRenderer, + DynamicAdminRenderer, + DynamicBrowsableAPIRenderer + ) features = (INCLUDE, EXCLUDE, FILTER, PAGE, PER_PAGE, SORT, SIDELOADING) meta = None filter_backends = (DynamicFilterBackend, DynamicSortingFilter) diff --git a/install_requires.txt b/install_requires.txt index 06889931..f799c6ca 100644 --- a/install_requires.txt +++ b/install_requires.txt @@ -1,4 +1,4 @@ Django>=1.7,<=1.10 -djangorestframework>=3.1.0,<=3.4.0 +djangorestframework>3.5.0 inflection==0.3.1 requests diff --git a/tests/serializers.py b/tests/serializers.py index e4fc2fac..fb7e18ce 100644 --- a/tests/serializers.py +++ b/tests/serializers.py @@ -199,6 +199,7 @@ class ProfileSerializer(DynamicModelSerializer): class Meta: model = Profile name = 'profile' + fields = '__all__' user = DynamicRelationField('UserSerializer') user_location_name = DynamicField( @@ -256,6 +257,7 @@ class HorseSerializer(DynamicModelSerializer): class Meta: model = Horse + fields = '__all__' name = 'horse' @@ -264,3 +266,4 @@ class ZebraSerializer(DynamicModelSerializer): class Meta: model = Zebra name = 'zebra' + fields = '__all__' diff --git a/tests/settings.py b/tests/settings.py index fa576133..7d2a8d95 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -36,7 +36,7 @@ 'django.contrib.contenttypes', 'django.contrib.auth', 'django.contrib.sites', - 'debug_toolbar', + # 'debug_toolbar', 'dynamic_rest', 'tests', ) @@ -45,6 +45,7 @@ 'PAGE_SIZE': 50, 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', + 'dynamic_rest.renderers.DynamicAdminRenderer', 'dynamic_rest.renderers.DynamicBrowsableAPIRenderer' ) } From 4fc0a9322f5d77e28532e643bbd6dffb0dd10ea2 Mon Sep 17 00:00:00 2001 From: aleontiev Date: Tue, 28 Feb 2017 15:32:06 -0800 Subject: [PATCH 002/171] WIP --- dynamic_rest/renderers.py | 36 ++++++++++++++---------------------- dynamic_rest/serializers.py | 5 +++++ dynamic_rest/utils.py | 2 ++ 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/dynamic_rest/renderers.py b/dynamic_rest/renderers.py index 47fc975c..996d524c 100644 --- a/dynamic_rest/renderers.py +++ b/dynamic_rest/renderers.py @@ -27,14 +27,16 @@ def get_context(self, data, media_type, context): class DynamicAdminRenderer(AdminRenderer): """Admin renderer override.""" - COLUMN_BLACKLIST = ('id', 'links', 'url') template = 'dynamic_rest/admin.html' + COLUMN_BLACKLIST = ('id', 'links') def get_context(self, data, media_type, context): - def add_url(result): + def process(result): if result.get('links', {}).get('self'): result['url'] = result['links']['self'] + data = unpack(data) + context = super(DynamicAdminRenderer, self).get_context( data, media_type, @@ -43,31 +45,21 @@ def add_url(result): # to account for the DREST envelope # (data is stored one level deeper than expected in the response) - results = unpack(context['results']) - if results is None: - style = 'detail' - header = {} - elif isinstance(results, list): + results = context['results'] + if isinstance(results, list): for result in results: - add_url(result) - header = results[0] if results else {} - style = 'list' + process(result) else: - add_url(results) - header = results - style = 'detail' + process(results) - columns = [ - key for key in header.keys() if key not in self.COLUMN_BLACKLIST + context['columns'] = [ + c for c in context['columns'] if c not in self.COLUMN_BLACKLIST ] - context['results'] = results - context['columns'] = columns - context['details'] = columns - context['style'] = style + context['details'] = context['columns'] return context - def get_raw_data_form(self, data, view, method, request): - print unpack(data) + def render_form_for_serializer(self, serializer): + serializer.disable_envelope() return super( DynamicAdminRenderer, self - ).get_raw_data_form(unpack(data), view, method, request) + ).render_form_for_serializer(serializer) diff --git a/dynamic_rest/serializers.py b/dynamic_rest/serializers.py index 089de2d8..1621cebf 100644 --- a/dynamic_rest/serializers.py +++ b/dynamic_rest/serializers.py @@ -275,6 +275,11 @@ def _dynamic_init(self, only_fields, include_fields, exclude_fields): # not sideloading this field self.request_fields[name] = True + def disable_envelope(self): + self.envelope = False + if hasattr(self, '_processed_data'): + del self._procesed_data + @classmethod def get_model(cls): """Get the model, if the serializer has one. diff --git a/dynamic_rest/utils.py b/dynamic_rest/utils.py index 674e17d6..bcbe0fac 100644 --- a/dynamic_rest/utils.py +++ b/dynamic_rest/utils.py @@ -20,4 +20,6 @@ def unpack(content): keys = [k for k in content.keys() if k != 'meta'] unpacked = content[keys[0]] + if hasattr(content, 'serializer'): + unpacked.serializer = content.serializer return unpacked From f949a2bdb094efecca949254e28176b010cf8144 Mon Sep 17 00:00:00 2001 From: aleontiev Date: Wed, 1 Mar 2017 16:10:55 -0800 Subject: [PATCH 003/171] processed data --- dynamic_rest/serializers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dynamic_rest/serializers.py b/dynamic_rest/serializers.py index 1621cebf..76e36bbe 100644 --- a/dynamic_rest/serializers.py +++ b/dynamic_rest/serializers.py @@ -66,7 +66,7 @@ def id_only(self): @property def data(self): """Get the data, after performing post-processing if necessary.""" - if not hasattr(self, '_processed_data'): + if getattr(self, '_processed_data', None) is None: data = super(DynamicListSerializer, self).data self._processed_data = ReturnDict( SideloadingProcessor(self, data).data, @@ -277,8 +277,7 @@ def _dynamic_init(self, only_fields, include_fields, exclude_fields): def disable_envelope(self): self.envelope = False - if hasattr(self, '_processed_data'): - del self._procesed_data + self._processed_data = None @classmethod def get_model(cls): @@ -604,7 +603,7 @@ def id_only(self): @property def data(self): - if not hasattr(self, '_processed_data'): + if getattr(self, '_processed_data', None) is None: data = super(WithDynamicSerializerMixin, self).data data = SideloadingProcessor( self, data From 850329921cb471cb2d22d54fb3d96c9ec5be8a4e Mon Sep 17 00:00:00 2001 From: Lynne Tye Date: Mon, 6 Mar 2017 11:04:07 -0800 Subject: [PATCH 004/171] Improve design for details view --- .../static/dynamic_rest/css/details.css | 57 ++++ dynamic_rest/static/images/logo-white.svg | 15 + .../templates/dynamic_rest/details.html | 261 ++++++++++++++++++ 3 files changed, 333 insertions(+) create mode 100644 dynamic_rest/static/dynamic_rest/css/details.css create mode 100644 dynamic_rest/static/images/logo-white.svg create mode 100644 dynamic_rest/templates/dynamic_rest/details.html diff --git a/dynamic_rest/static/dynamic_rest/css/details.css b/dynamic_rest/static/dynamic_rest/css/details.css new file mode 100644 index 00000000..ee780f08 --- /dev/null +++ b/dynamic_rest/static/dynamic_rest/css/details.css @@ -0,0 +1,57 @@ +/*temporary css. remove once we decide whether to use flexbox*/ +.display-flex { + display: flex; + justify-content: space-between; +} + +/**/ + +/*move to global css file*/ + +a { + cursor: pointer; +} + +.breadcrumb>li+li:before { + content: "\003e" !important; +} + +.navbar-brand { + height: auto; + padding: 0; +} + +.logo { + height: 60px; + padding: 15px; +} + +.breadcrumb { + background-color: transparent; + display: inline-block; + margin: 12px auto 0 !important; + width: auto; +} + +#content { + display: flex; + margin-top: 100px; +} + +.sidebar-actions { + align-items: stretch; + display: flex; + flex-wrap: wrap; + height: 100%; + justify-content: space-between; + padding-top: 10px; + width: 110px; +} + +.content-main { + width: calc(100% - 100px); +} + +.sidebar-action { + margin-bottom: 20px; +} diff --git a/dynamic_rest/static/images/logo-white.svg b/dynamic_rest/static/images/logo-white.svg new file mode 100644 index 00000000..6bc315c0 --- /dev/null +++ b/dynamic_rest/static/images/logo-white.svg @@ -0,0 +1,15 @@ + + + + white_full + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/dynamic_rest/templates/dynamic_rest/details.html b/dynamic_rest/templates/dynamic_rest/details.html new file mode 100644 index 00000000..db007370 --- /dev/null +++ b/dynamic_rest/templates/dynamic_rest/details.html @@ -0,0 +1,261 @@ +{% load staticfiles %} +{% load i18n %} +{% load rest_framework %} + + + + + + {% block head %} + + {% block meta %} + + + {% endblock %} + + {% block title %}Django REST framework{% endblock %} + + {% block style %} + {% block bootstrap_theme %} + + + {% endblock %} + + + + {% endblock %} + + {% endblock %} + + + {% block body %} + +
+ {% block navbar %} + + {% endblock %} + +
+ + +
+ + + +
+ + +
+ {% block description %} + {{ description }} + {% endblock %} +
+ + {% if paginator %} + + {% endif %} + +
+ {% if style == 'list' %} + {% include "rest_framework/admin/list.html" %} + {% else %} + {% include "rest_framework/admin/detail.html" %} + {% endif %} +
+ + {% if paginator %} + + {% endif %} +
+
+ +
+
+ + + + + + + + {% if error_form %} + + + {% endif %} + + {% if filter_form %} + {{ filter_form }} + {% endif %} + + {% block script %} + + + + + + + + + {% endblock %} + + {% endblock %} + From 60601bbfeb379e4087318e32a368280bf1433eef Mon Sep 17 00:00:00 2001 From: Lynne Tye Date: Mon, 6 Mar 2017 11:47:08 -0800 Subject: [PATCH 005/171] Provide interface for uploading file --- .../templates/dynamic_rest/details.html | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dynamic_rest/templates/dynamic_rest/details.html b/dynamic_rest/templates/dynamic_rest/details.html index db007370..fd79f6bc 100644 --- a/dynamic_rest/templates/dynamic_rest/details.html +++ b/dynamic_rest/templates/dynamic_rest/details.html @@ -103,6 +103,12 @@ {% endif %} + {% if style == 'list' %} + + {% endif %} + {% if put_form %} + + +
+ + +
+ + + +