django-rest-spreadsheets 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. django_rest_spreadsheets-1.0.0/LICENCE +7 -0
  2. django_rest_spreadsheets-1.0.0/MANIFEST.in +2 -0
  3. django_rest_spreadsheets-1.0.0/PKG-INFO +33 -0
  4. django_rest_spreadsheets-1.0.0/README.md +14 -0
  5. django_rest_spreadsheets-1.0.0/django_rest_spreadsheets.egg-info/PKG-INFO +33 -0
  6. django_rest_spreadsheets-1.0.0/django_rest_spreadsheets.egg-info/SOURCES.txt +38 -0
  7. django_rest_spreadsheets-1.0.0/django_rest_spreadsheets.egg-info/dependency_links.txt +1 -0
  8. django_rest_spreadsheets-1.0.0/django_rest_spreadsheets.egg-info/top_level.txt +1 -0
  9. django_rest_spreadsheets-1.0.0/pyproject.toml +19 -0
  10. django_rest_spreadsheets-1.0.0/setup.cfg +4 -0
  11. django_rest_spreadsheets-1.0.0/setup.py +24 -0
  12. django_rest_spreadsheets-1.0.0/spreadsheet/__init__.py +0 -0
  13. django_rest_spreadsheets-1.0.0/spreadsheet/fields.py +196 -0
  14. django_rest_spreadsheets-1.0.0/spreadsheet/migrations/__init__.py +0 -0
  15. django_rest_spreadsheets-1.0.0/spreadsheet/schema.py +116 -0
  16. django_rest_spreadsheets-1.0.0/spreadsheet/serializers.py +142 -0
  17. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/jquery-ui.css +1312 -0
  18. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/jquery-ui.js +18706 -0
  19. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/jquery.datetimepicker.css +568 -0
  20. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/jquery.datetimepicker.full.js +2887 -0
  21. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/jquery.datetimepicker.js +2656 -0
  22. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/jquery.datetimepicker.min.css +1 -0
  23. django_rest_spreadsheets-1.0.0/spreadsheet/static/deps/moment.js +4506 -0
  24. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/css/menu.css +46 -0
  25. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/css/reset.css +5 -0
  26. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/css/screen.css +40 -0
  27. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/css/spreadsheet.css +246 -0
  28. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/img/cancel.svg +5 -0
  29. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/img/create.svg +3 -0
  30. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/img/delete.svg +3 -0
  31. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/img/icon.png +0 -0
  32. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/img/update.svg +3 -0
  33. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/js/RestApi6.js +61 -0
  34. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/js/ScreenStack.js +25 -0
  35. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/js/SpreadSheet.js +543 -0
  36. django_rest_spreadsheets-1.0.0/spreadsheet/static/spreadsheet/js/menu.js +12 -0
  37. django_rest_spreadsheets-1.0.0/spreadsheet/templates/index.html +77 -0
  38. django_rest_spreadsheets-1.0.0/spreadsheet/urls.py +10 -0
  39. django_rest_spreadsheets-1.0.0/spreadsheet/util.py +16 -0
  40. django_rest_spreadsheets-1.0.0/spreadsheet/views.py +137 -0
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Gerben van Eerten
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ recursive-include spreadsheet/static *
2
+ recursive-include spreadsheet/templates *
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-rest-spreadsheets
3
+ Version: 1.0.0
4
+ Summary: Spreadsheets based on django rest framework
5
+ Home-page: https://github.com/g3rb3n/django-rest-spreadsheets/
6
+ Author: gerben van Eerten
7
+ Author-email: Gerben van Eerten <info@gerbenvaneerten.nl>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/g3rb3n/django-rest-spreadsheets
10
+ Project-URL: Issues, https://github.com/g3rb3n/django-rest-spreadsheets/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENCE
16
+ Dynamic: author
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+
20
+ # Introduction
21
+ Spreadsheets based on django models and django rest framework.
22
+
23
+ # Setup
24
+ `make setup`
25
+
26
+ # Run
27
+ `make run`
28
+
29
+ # Open browser
30
+ `make open-browser`
31
+
32
+ # Testing
33
+ `make test`
@@ -0,0 +1,14 @@
1
+ # Introduction
2
+ Spreadsheets based on django models and django rest framework.
3
+
4
+ # Setup
5
+ `make setup`
6
+
7
+ # Run
8
+ `make run`
9
+
10
+ # Open browser
11
+ `make open-browser`
12
+
13
+ # Testing
14
+ `make test`
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-rest-spreadsheets
3
+ Version: 1.0.0
4
+ Summary: Spreadsheets based on django rest framework
5
+ Home-page: https://github.com/g3rb3n/django-rest-spreadsheets/
6
+ Author: gerben van Eerten
7
+ Author-email: Gerben van Eerten <info@gerbenvaneerten.nl>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/g3rb3n/django-rest-spreadsheets
10
+ Project-URL: Issues, https://github.com/g3rb3n/django-rest-spreadsheets/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENCE
16
+ Dynamic: author
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+
20
+ # Introduction
21
+ Spreadsheets based on django models and django rest framework.
22
+
23
+ # Setup
24
+ `make setup`
25
+
26
+ # Run
27
+ `make run`
28
+
29
+ # Open browser
30
+ `make open-browser`
31
+
32
+ # Testing
33
+ `make test`
@@ -0,0 +1,38 @@
1
+ LICENCE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ django_rest_spreadsheets.egg-info/PKG-INFO
7
+ django_rest_spreadsheets.egg-info/SOURCES.txt
8
+ django_rest_spreadsheets.egg-info/dependency_links.txt
9
+ django_rest_spreadsheets.egg-info/top_level.txt
10
+ spreadsheet/__init__.py
11
+ spreadsheet/fields.py
12
+ spreadsheet/schema.py
13
+ spreadsheet/serializers.py
14
+ spreadsheet/urls.py
15
+ spreadsheet/util.py
16
+ spreadsheet/views.py
17
+ spreadsheet/migrations/__init__.py
18
+ spreadsheet/static/deps/jquery-ui.css
19
+ spreadsheet/static/deps/jquery-ui.js
20
+ spreadsheet/static/deps/jquery.datetimepicker.css
21
+ spreadsheet/static/deps/jquery.datetimepicker.full.js
22
+ spreadsheet/static/deps/jquery.datetimepicker.js
23
+ spreadsheet/static/deps/jquery.datetimepicker.min.css
24
+ spreadsheet/static/deps/moment.js
25
+ spreadsheet/static/spreadsheet/css/menu.css
26
+ spreadsheet/static/spreadsheet/css/reset.css
27
+ spreadsheet/static/spreadsheet/css/screen.css
28
+ spreadsheet/static/spreadsheet/css/spreadsheet.css
29
+ spreadsheet/static/spreadsheet/img/cancel.svg
30
+ spreadsheet/static/spreadsheet/img/create.svg
31
+ spreadsheet/static/spreadsheet/img/delete.svg
32
+ spreadsheet/static/spreadsheet/img/icon.png
33
+ spreadsheet/static/spreadsheet/img/update.svg
34
+ spreadsheet/static/spreadsheet/js/RestApi6.js
35
+ spreadsheet/static/spreadsheet/js/ScreenStack.js
36
+ spreadsheet/static/spreadsheet/js/SpreadSheet.js
37
+ spreadsheet/static/spreadsheet/js/menu.js
38
+ spreadsheet/templates/index.html
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "django-rest-spreadsheets"
3
+ version = "1.0.0"
4
+ authors = [
5
+ { name="Gerben van Eerten", email="info@gerbenvaneerten.nl" },
6
+ ]
7
+ description = "Spreadsheets based on django rest framework"
8
+ readme = "README.md"
9
+ requires-python = ">=3.9"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICENCE*"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/g3rb3n/django-rest-spreadsheets"
19
+ Issues = "https://github.com/g3rb3n/django-rest-spreadsheets/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ import setuptools
2
+
3
+ with open("README.md", "r") as fh:
4
+ long_description = fh.read()
5
+
6
+ setuptools.setup(
7
+ name="django-rest-spreadsheets",
8
+ version="1.0.0",
9
+ author="gerben van Eerten",
10
+ author_email="info@gerbenvaneerten.nl",
11
+ description="Spreadsheets based on django rest framework",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ url="https://github.com/g3rb3n/django-rest-spreadsheets/",
15
+ packages=setuptools.find_packages(exclude=['testproject*']),
16
+ package_data={'django-rest-spreadsheets': ['spreadsheet/static', 'spreadsheet/templates', 'spreadsheet/migrations']},
17
+ include_package_data=True,
18
+
19
+ classifiers=[
20
+ "Programming Language :: Python :: 3",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ ],
24
+ )
File without changes
@@ -0,0 +1,196 @@
1
+ import logging
2
+
3
+ from django.utils.translation import gettext_lazy as _
4
+ from django.urls import NoReverseMatch, Resolver404, get_script_prefix, resolve
5
+ from django.utils.encoding import uri_to_iri
6
+ from rest_framework import relations
7
+ from rest_framework.reverse import reverse
8
+ from urllib.parse import urlparse
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class Ref(str):
15
+ """
16
+ A string like object that additionally has an associated name.
17
+ We use this for hyperlinked URLs that may render as a named link
18
+ in some contexts, or render as a plain URL in others.
19
+ """
20
+ def __new__(self, url, obj):
21
+ ret = super().__new__(self, url)
22
+ ret.obj = obj
23
+ return ret
24
+
25
+ def __getnewargs__(self):
26
+ return(str(self), self.name,)
27
+
28
+ @property
29
+ def name(self):
30
+ # This ensures that we only called `__str__` lazily,
31
+ # as in some cases calling __str__ on a model instances *might*
32
+ # involve a database lookup.
33
+ return str(self.obj)
34
+
35
+ is_hyperlink = True
36
+
37
+
38
+ class RefRelatedField(relations.RelatedField):
39
+ lookup_field = 'pk'
40
+ view_name = None
41
+
42
+ default_error_messages = {
43
+ 'required': _('This field is required.'),
44
+ 'no_match': _('Invalid hyperlink - No URL match.'),
45
+ 'incorrect_match': _('Invalid hyperlink - Incorrect URL match.'),
46
+ 'does_not_exist': _('Invalid hyperlink - Object does not exist.'),
47
+ 'incorrect_type': _('Incorrect type. Expected URL string, received {data_type}.'),
48
+ }
49
+
50
+ def __init__(self, view_name=None, **kwargs):
51
+ assert view_name is not None, 'The `view_name` argument is required.'
52
+ self.view_name = view_name
53
+ self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
54
+ self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
55
+ self.format = kwargs.pop('format', None)
56
+
57
+ # We include this simply for dependency injection in tests.
58
+ # We can't add it as a class attributes or it would expect an
59
+ # implicit `self` argument to be passed.
60
+ self.reverse = reverse
61
+
62
+ super(RefRelatedField, self).__init__(**kwargs)
63
+
64
+ def use_pk_only_optimization(self):
65
+ return False
66
+ #return self.lookup_field == 'pk'
67
+
68
+ def get_object(self, view_name, view_args, view_kwargs):
69
+ """
70
+ Return the object corresponding to a matched URL.
71
+
72
+ Takes the matched URL conf arguments, and should return an
73
+ object instance, or raise an `ObjectDoesNotExist` exception.
74
+ """
75
+ lookup_value = view_kwargs[self.lookup_url_kwarg]
76
+ lookup_kwargs = {self.lookup_field: lookup_value}
77
+ return self.get_queryset().get(**lookup_kwargs)
78
+
79
+ def get_url(self, obj, view_name, request, format):
80
+ """
81
+ Given an object, return the URL that hyperlinks to the object.
82
+
83
+ May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
84
+ attributes are not configured to correctly match the URL conf.
85
+ """
86
+ # Unsaved objects will not yet have a valid URL.
87
+ if hasattr(obj, 'pk') and obj.pk in (None, ''):
88
+ return None
89
+
90
+ lookup_value = getattr(obj, self.lookup_field)
91
+ kwargs = {self.lookup_url_kwarg: lookup_value}
92
+ return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
93
+
94
+ def to_internal_value(self, data):
95
+ request = self.context.get('request', None)
96
+ try:
97
+ http_prefix = data.startswith(('http:', 'https:'))
98
+ except AttributeError:
99
+ self.fail('incorrect_type', data_type=type(data).__name__)
100
+
101
+ if http_prefix:
102
+ # If needed convert absolute URLs to relative path
103
+ data = urlparse(data).path
104
+ prefix = get_script_prefix()
105
+ if data.startswith(prefix):
106
+ data = '/' + data[len(prefix):]
107
+
108
+ data = uri_to_iri(data)
109
+
110
+ try:
111
+ match = resolve(data)
112
+ except Resolver404:
113
+ self.fail('no_match')
114
+
115
+ try:
116
+ expected_viewname = request.versioning_scheme.get_versioned_viewname(
117
+ self.view_name, request
118
+ )
119
+ except AttributeError:
120
+ expected_viewname = self.view_name
121
+
122
+ if match.view_name != expected_viewname:
123
+ self.fail('incorrect_match')
124
+
125
+ try:
126
+ return self.get_object(match.view_name, match.args, match.kwargs)
127
+ except (ObjectDoesNotExist, TypeError, ValueError):
128
+ self.fail('does_not_exist')
129
+
130
+ def to_representation(self, value):
131
+ assert 'request' in self.context, (
132
+ "`%s` requires the request in the serializer"
133
+ " context. Add `context={'request': request}` when instantiating "
134
+ "the serializer." % self.__class__.__name__
135
+ )
136
+
137
+ request = self.context['request']
138
+ format = self.context.get('format', None)
139
+
140
+ # By default use whatever format is given for the current context
141
+ # unless the target is a different type to the source.
142
+ #
143
+ # Eg. Consider a HyperlinkedIdentityField pointing from a json
144
+ # representation to an html property of that representation...
145
+ #
146
+ # '/snippets/1/' should link to '/snippets/1/highlight/'
147
+ # ...but...
148
+ # '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
149
+ if format and self.format and self.format != format:
150
+ format = self.format
151
+
152
+ # Return the hyperlink, or error if incorrectly configured.
153
+ try:
154
+ url = self.get_url(value, self.view_name, request, format)
155
+ logger.debug(url)
156
+ logger.debug(value)
157
+ except NoReverseMatch:
158
+ msg = (
159
+ 'Could not resolve URL for hyperlinked relationship using '
160
+ 'view name "%s". You may have failed to include the related '
161
+ 'model in your API, or incorrectly configured the '
162
+ '`lookup_field` attribute on this field.'
163
+ )
164
+ if value in ('', None):
165
+ value_string = {'': 'the empty string', None: 'None'}[value]
166
+ msg += (
167
+ " WARNING: The value of the field on the model instance "
168
+ "was %s, which may be why it didn't match any "
169
+ "entries in your URL conf." % value_string
170
+ )
171
+ raise ImproperlyConfigured(msg % self.view_name)
172
+
173
+ if url is None:
174
+ return None
175
+
176
+ return Ref(url, value)
177
+
178
+
179
+ class RefIdentityField(RefRelatedField):
180
+ """
181
+ A read-only field that represents the identity URL for an object, itself.
182
+
183
+ This is in contrast to `HyperlinkedRelatedField` which represents the
184
+ URL of relationships to other objects.
185
+ """
186
+
187
+ def __init__(self, view_name=None, **kwargs):
188
+ assert view_name is not None, 'The `view_name` argument is required.'
189
+ kwargs['read_only'] = True
190
+ kwargs['source'] = '*'
191
+ super().__init__(view_name, **kwargs)
192
+
193
+ def use_pk_only_optimization(self):
194
+ # We have the complete object instance already. We don't need
195
+ # to run the 'only get the pk for this relationship' code.
196
+ return False
@@ -0,0 +1,116 @@
1
+ import django
2
+ import inspect
3
+ import logging
4
+ import sys
5
+
6
+ from collections import OrderedDict
7
+ from rest_framework import serializers
8
+ from rest_framework import fields as rffields
9
+
10
+ from . import util
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def generate_field_schema(field):
17
+ data_type = get_type(field)
18
+ schema = {
19
+ 'name': field.name,
20
+ 'type': data_type
21
+ }
22
+ if field.name == 'id':
23
+ schema['isKey'] = True
24
+ if isinstance(field, django.db.models.fields.Field):
25
+ schema['title'] = field.verbose_name
26
+ if isinstance(field, django.db.models.fields.related.ForeignKey):
27
+ schema['$ref'] = field.related_model.__name__
28
+ schema['count'] = field.related_model.objects.count()
29
+ schema['multiple'] = False
30
+ if isinstance(field, django.db.models.fields.related.ManyToManyField):
31
+ schema['$ref'] = field.related_model.__name__
32
+ schema['multiple'] = True
33
+ if isinstance(field, django.db.models.fields.reverse_related.ForeignObjectRel):
34
+ if (field.remote_field.name[-4:] == '_ptr'):
35
+ return None
36
+ schema['$ref'] = field.related_model.__name__
37
+ schema['multiple'] = field.multiple
38
+ schema['title'] = field.related_model._meta.verbose_name_plural
39
+ schema['lookup'] = field.remote_field.name
40
+ if hasattr(field, 'blank'):
41
+ schema['required'] = not field.blank
42
+ if hasattr(field, 'choices') and field.choices:
43
+ sorted_choices = sorted(field.get_choices(), key=lambda tup: tup[1])
44
+ schema['type'] = 'oneOf'
45
+ schema['oneOf'] = OrderedDict(sorted_choices)
46
+ if data_type == 'string':
47
+ schema['length'] = field.max_length
48
+ return schema
49
+
50
+
51
+ def generate_model_schema(model_class):
52
+ name = model_class.__name__
53
+ model_schema = {
54
+ '$schema': 'http://json-schema.org/draft-06/schema#',
55
+ 'title': name,
56
+ 'description': name,
57
+ 'type': 'object',
58
+ 'properties': {}
59
+ }
60
+ fields = model_class._meta.get_fields()
61
+ required = []
62
+ order = []
63
+ for field in fields:
64
+ if field.name[-4:] == '_ptr':
65
+ continue
66
+ field_schema = generate_field_schema(field)
67
+ if not field_schema:
68
+ continue
69
+ order.append(field.name)
70
+ model_schema['properties'][field.name] = field_schema
71
+ model_schema['fields'] = order
72
+ return model_schema
73
+
74
+
75
+ def get_type(field):
76
+ t = field.get_internal_type()
77
+ if t == 'CharField': return 'string'
78
+ if t == 'BooleanField': return 'boolean'
79
+ if t == 'IntegerField': return 'integer'
80
+ if t == 'DecimalField': return 'number'
81
+ if t == 'DateTimeField': return 'datetime'
82
+ if t == 'DateField': return 'date'
83
+ if t == 'TimeField': return 'time'
84
+ if t == 'AutoField': return 'integer'
85
+ if t == 'ForeignKey': return 'object'
86
+ if t == 'OneToOneField': return 'object'
87
+ if t == 'ManyToManyField': return 'many'
88
+ if t == 'OneToOneRel': return 'object'
89
+ if t == 'OneToManyRel': return 'object'
90
+ if t == 'ManyToOneRel': return 'object'
91
+ if t == 'ManyToManyRel': return 'object'
92
+ logger.error('Unsupported field in get_type %s for %s', t, field)
93
+ return t
94
+
95
+
96
+ def get_django_models(module):
97
+ classes = inspect.getmembers(module, inspect.isclass)
98
+ models = {}
99
+ for name, cls in classes:
100
+ mro = inspect.getmro(cls)
101
+ if django.db.models.base.Model in mro:
102
+ models[name] = cls
103
+ return models
104
+
105
+
106
+ def generate(models_module):
107
+ schema = {}
108
+ schema['$schema'] = 'http://json-schema.org/draft-06/schema#'
109
+ title = ''
110
+ model_classes = get_django_models(models_module)
111
+ for name, model_class in model_classes.items():
112
+ if util.model_is_abstract(model_class):
113
+ continue
114
+ logger.info('Schema: generate %s' % name)
115
+ schema[name] = generate_model_schema(model_class)
116
+ return schema
@@ -0,0 +1,142 @@
1
+ import sys, inspect
2
+ import logging
3
+
4
+ from django.utils.translation import gettext_lazy as _
5
+ from rest_framework import serializers
6
+ from rest_framework import fields as rffields
7
+ from rest_framework.reverse import reverse
8
+ from rest_framework.utils.field_mapping import (
9
+ get_nested_relation_kwargs,
10
+ get_relation_kwargs, get_url_kwargs
11
+ )
12
+
13
+ from .fields import *
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class RefModelSerializer(serializers.ModelSerializer):
20
+ """
21
+ A type of `ModelSerializer` that uses hyperlinked relationships instead
22
+ of primary key relationships. Specifically:
23
+
24
+ * A 'url' field is included instead of the 'id' field.
25
+ * Relationships to other instances are hyperlinks, instead of primary keys.
26
+ """
27
+ #setattr(serializers.ModelSerializer, 'serializer_url_field', RefRelatedField)
28
+ serializer_related_field = RefRelatedField
29
+ meta_serializer_field = RefRelatedField
30
+ meta_serializer_identity_field = RefIdentityField
31
+ meta_field_name = '_'
32
+
33
+ def build_meta_field(self, field_name, model_class):
34
+ """
35
+ Create a field representing the object's own URL.
36
+ """
37
+ #field_class = self.serializer_url_field
38
+ field_class = self.meta_serializer_identity_field
39
+ field_kwargs = get_url_kwargs(model_class)
40
+
41
+ return field_class, field_kwargs
42
+
43
+ def build_relational_field(self, field_name, relation_info):
44
+ """
45
+ Create fields for forward and reverse relationships.
46
+ """
47
+ field_class = self.serializer_related_field
48
+ field_kwargs = get_relation_kwargs(field_name, relation_info)
49
+
50
+ to_field = field_kwargs.pop('to_field', None)
51
+ if to_field and not relation_info.reverse and not relation_info.related_model._meta.get_field(to_field).primary_key:
52
+ field_kwargs['slug_field'] = to_field
53
+ field_class = self.serializer_related_to_field
54
+
55
+ # `view_name` is only valid for hyperlinked relationships.
56
+ if not issubclass(field_class, RefRelatedField):
57
+ field_kwargs.pop('view_name', None)
58
+
59
+ return field_class, field_kwargs
60
+
61
+ def to_representation(self, instance):
62
+ representation = super().to_representation(instance);
63
+ representation['_str'] = str(instance)
64
+ #representation['_'] = {
65
+ # 'url': '',
66
+ # 'id': instance.pk,
67
+ # 'string': str(instance)
68
+ #}
69
+ #RefRelatedField().to_representation(instance)
70
+ return representation
71
+
72
+ def build_field(self, field_name, info, model_class, nested_depth):
73
+ """
74
+ Return a two tuple of (cls, kwargs) to build a serializer field with.
75
+ """
76
+ if field_name == '_':
77
+ return self.build_meta_field(field_name, model_class)
78
+ if field_name == '_str':
79
+ return rffields.CharField, {'read_only': True}
80
+ return super().build_field(field_name, info, model_class, nested_depth)
81
+
82
+ def get_fields(self):
83
+ fields = super().get_fields()
84
+ logger.debug('get_fields')
85
+ logger.debug(fields)
86
+ return fields
87
+
88
+ def get_default_field_names(self, declared_fields, model_info):
89
+ """
90
+ Return the default list of field names that will be used if the
91
+ `Meta.fields` option is not specified.
92
+ """
93
+ logger.debug('get_default_field_names')
94
+ logger.debug(model_info)
95
+ return (
96
+ ['_'] +
97
+ ['_str'] +
98
+ list(declared_fields) +
99
+ list(model_info.fields) +
100
+ list(model_info.forward_relations) +
101
+ list(model_info.reverse_relations)
102
+ )
103
+
104
+ def build_nested_field(self, field_name, relation_info, nested_depth):
105
+ """
106
+ Create nested fields for forward and reverse relationships.
107
+ """
108
+ class NestedSerializer(RefModelSerializer):
109
+ class Meta:
110
+ model = relation_info.related_model
111
+ depth = nested_depth - 1
112
+ fields = '__all__'
113
+
114
+ field_class = NestedSerializer
115
+ field_kwargs = get_nested_relation_kwargs(relation_info)
116
+
117
+ return field_class, field_kwargs
118
+
119
+
120
+ def s(m):
121
+ class Serializer(RefModelSerializer):
122
+ class Meta:
123
+ model = m
124
+ exclude = ()
125
+
126
+ return Serializer
127
+
128
+
129
+ def generate(models_module, serializers_module):
130
+ generated = {}
131
+ model_classes = inspect.getmembers(models_module, inspect.isclass)
132
+ #serializer_module = sys.modules[__name__]
133
+ serializer_classes = dir(serializers_module)
134
+ for name, cls in model_classes:
135
+ logger.debug('Serializers: generate %s' % name)
136
+ if name + 'Serializer' in serializer_classes:
137
+ logger.info('already defined %s' % name)
138
+ generated[name] = getattr(serializers_module, name + 'Serializer')
139
+ else:
140
+ logger.info('generate %s' % name)
141
+ generated[name] = s(cls)
142
+ return generated