Эх сурвалжийг харах

[libs] Adding djangorestframework 3.9.4

Romain Rigaux 5 жил өмнө
parent
commit
7986f2aa1a
100 өөрчлөгдсөн 11621 нэмэгдсэн , 0 устгасан
  1. 29 0
      desktop/core/ext-py/djangorestframework-3.9.4/LICENSE.md
  2. 7 0
      desktop/core/ext-py/djangorestframework-3.9.4/MANIFEST.in
  3. 259 0
      desktop/core/ext-py/djangorestframework-3.9.4/PKG-INFO
  4. 228 0
      desktop/core/ext-py/djangorestframework-3.9.4/README.md
  5. 33 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/__init__.py
  6. 10 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/apps.py
  7. 228 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authentication.py
  8. 1 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/__init__.py
  9. 12 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/admin.py
  10. 7 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/apps.py
  11. 0 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/management/__init__.py
  12. 0 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/management/commands/__init__.py
  13. 45 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/management/commands/drf_create_token.py
  14. 26 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/migrations/0001_initial.py
  15. 34 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py
  16. 0 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/migrations/__init__.py
  17. 41 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/models.py
  18. 34 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/serializers.py
  19. 50 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/views.py
  20. 21 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/checks.py
  21. 332 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/compat.py
  22. 255 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/decorators.py
  23. 84 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/documentation.py
  24. 261 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/exceptions.py
  25. 1931 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/fields.py
  26. 329 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/filters.py
  27. 293 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/generics.py
  28. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ach/LC_MESSAGES/django.mo
  29. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ar/LC_MESSAGES/django.mo
  30. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/be/LC_MESSAGES/django.mo
  31. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ca/LC_MESSAGES/django.mo
  32. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ca_ES/LC_MESSAGES/django.mo
  33. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/cs/LC_MESSAGES/django.mo
  34. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/da/LC_MESSAGES/django.mo
  35. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/de/LC_MESSAGES/django.mo
  36. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/el/LC_MESSAGES/django.mo
  37. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/el_GR/LC_MESSAGES/django.mo
  38. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en/LC_MESSAGES/django.mo
  39. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en_AU/LC_MESSAGES/django.mo
  40. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en_CA/LC_MESSAGES/django.mo
  41. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en_US/LC_MESSAGES/django.mo
  42. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/es/LC_MESSAGES/django.mo
  43. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/et/LC_MESSAGES/django.mo
  44. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fa/LC_MESSAGES/django.mo
  45. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fa_IR/LC_MESSAGES/django.mo
  46. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fi/LC_MESSAGES/django.mo
  47. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fr/LC_MESSAGES/django.mo
  48. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fr_CA/LC_MESSAGES/django.mo
  49. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/gl/LC_MESSAGES/django.mo
  50. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/gl_ES/LC_MESSAGES/django.mo
  51. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/he_IL/LC_MESSAGES/django.mo
  52. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/hu/LC_MESSAGES/django.mo
  53. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/id/LC_MESSAGES/django.mo
  54. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/it/LC_MESSAGES/django.mo
  55. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ja/LC_MESSAGES/django.mo
  56. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo
  57. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/lv/LC_MESSAGES/django.mo
  58. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/mk/LC_MESSAGES/django.mo
  59. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/nb/LC_MESSAGES/django.mo
  60. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/nl/LC_MESSAGES/django.mo
  61. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/nn/LC_MESSAGES/django.mo
  62. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/no/LC_MESSAGES/django.mo
  63. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pl/LC_MESSAGES/django.mo
  64. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pt/LC_MESSAGES/django.mo
  65. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo
  66. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pt_PT/LC_MESSAGES/django.mo
  67. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ro/LC_MESSAGES/django.mo
  68. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ru/LC_MESSAGES/django.mo
  69. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/sk/LC_MESSAGES/django.mo
  70. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/sl/LC_MESSAGES/django.mo
  71. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/sv/LC_MESSAGES/django.mo
  72. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/tr/LC_MESSAGES/django.mo
  73. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/tr_TR/LC_MESSAGES/django.mo
  74. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/uk/LC_MESSAGES/django.mo
  75. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/vi/LC_MESSAGES/django.mo
  76. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo
  77. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_Hans/LC_MESSAGES/django.mo
  78. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_Hant/LC_MESSAGES/django.mo
  79. BIN
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_TW/LC_MESSAGES/django.mo
  80. 0 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/management/__init__.py
  81. 0 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/management/commands/__init__.py
  82. 41 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/management/commands/generateschema.py
  83. 153 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/metadata.py
  84. 97 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/mixins.py
  85. 97 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/negotiation.py
  86. 822 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/pagination.py
  87. 227 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/parsers.py
  88. 304 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/permissions.py
  89. 561 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/relations.py
  90. 1051 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/renderers.py
  91. 451 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/request.py
  92. 105 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/response.py
  93. 69 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/reverse.py
  94. 396 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/routers.py
  95. 49 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/__init__.py
  96. 454 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/generators.py
  97. 511 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/inspectors.py
  98. 24 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/utils.py
  99. 41 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/views.py
  100. 1618 0
      desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/serializers.py

+ 29 - 0
desktop/core/ext-py/djangorestframework-3.9.4/LICENSE.md

@@ -0,0 +1,29 @@
+# License
+
+Copyright © 2011-present, [Encode OSS Ltd](https://www.encode.io/).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 7 - 0
desktop/core/ext-py/djangorestframework-3.9.4/MANIFEST.in

@@ -0,0 +1,7 @@
+include README.md
+include LICENSE.md
+recursive-include rest_framework/static *.js *.css *.png *.ico *.eot *.svg *.ttf *.woff *.woff2
+recursive-include rest_framework/templates *.html schema.js
+recursive-include rest_framework/locale *.mo
+global-exclude __pycache__
+global-exclude *.py[co]

+ 259 - 0
desktop/core/ext-py/djangorestframework-3.9.4/PKG-INFO

@@ -0,0 +1,259 @@
+Metadata-Version: 2.1
+Name: djangorestframework
+Version: 3.9.4
+Summary: Web APIs for Django, made easy.
+Home-page: https://www.django-rest-framework.org/
+Author: Tom Christie
+Author-email: tom@tomchristie.com
+License: BSD
+Description: # [Django REST framework][docs]
+        
+        [![build-status-image]][travis]
+        [![coverage-status-image]][codecov]
+        [![pypi-version]][pypi]
+        
+        **Awesome web-browsable Web APIs.**
+        
+        Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
+        
+        ---
+        
+        # Funding
+        
+        REST framework is a *collaboratively funded project*. If you use
+        REST framework commercially we strongly encourage you to invest in its
+        continued development by [signing up for a paid plan][funding].
+        
+        The initial aim is to provide a single full-time position on REST framework.
+        *Every single sign-up makes a significant impact towards making that possible.*
+        
+        [![][sentry-img]][sentry-url]
+        [![][stream-img]][stream-url]
+        [![][rollbar-img]][rollbar-url]
+        [![][cadre-img]][cadre-url]
+        [![][kloudless-img]][kloudless-url]
+        [![][release-history-img]][release-history-url]
+        [![][lightson-img]][lightson-url]
+        
+        Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [Release History][release-history-url], and [Lights On Software][lightson-url].
+        
+        ---
+        
+        # Overview
+        
+        Django REST framework is a powerful and flexible toolkit for building Web APIs.
+        
+        Some reasons you might want to use REST framework:
+        
+        * The [Web browsable API][sandbox] is a huge usability win for your developers.
+        * [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
+        * [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
+        * Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
+        * [Extensive documentation][docs], and [great community support][group].
+        
+        There is a live example API for testing purposes, [available here][sandbox].
+        
+        **Below**: *Screenshot from the browsable API*
+        
+        ![Screenshot][image]
+        
+        ----
+        
+        # Requirements
+        
+        * Python (2.7, 3.4, 3.5, 3.6, 3.7)
+        * Django (1.11, 2.0, 2.1, 2.2)
+        
+        We **highly recommend** and only officially support the latest patch release of
+        each Python and Django series.
+        
+        # Installation
+        
+        Install using `pip`...
+        
+            pip install djangorestframework
+        
+        Add `'rest_framework'` to your `INSTALLED_APPS` setting.
+        
+            INSTALLED_APPS = (
+                ...
+                'rest_framework',
+            )
+        
+        # Example
+        
+        Let's take a look at a quick example of using REST framework to build a simple model-backed API for accessing users and groups.
+        
+        Startup up a new project like so...
+        
+            pip install django
+            pip install djangorestframework
+            django-admin startproject example .
+            ./manage.py migrate
+            ./manage.py createsuperuser
+        
+        
+        Now edit the `example/urls.py` module in your project:
+        
+        ```python
+        from django.conf.urls import url, include
+        from django.contrib.auth.models import User
+        from rest_framework import serializers, viewsets, routers
+        
+        # Serializers define the API representation.
+        class UserSerializer(serializers.HyperlinkedModelSerializer):
+            class Meta:
+                model = User
+                fields = ('url', 'username', 'email', 'is_staff')
+        
+        
+        # ViewSets define the view behavior.
+        class UserViewSet(viewsets.ModelViewSet):
+            queryset = User.objects.all()
+            serializer_class = UserSerializer
+        
+        
+        # Routers provide a way of automatically determining the URL conf.
+        router = routers.DefaultRouter()
+        router.register(r'users', UserViewSet)
+        
+        
+        # Wire up our API using automatic URL routing.
+        # Additionally, we include login URLs for the browsable API.
+        urlpatterns = [
+            url(r'^', include(router.urls)),
+            url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
+        ]
+        ```
+        
+        We'd also like to configure a couple of settings for our API.
+        
+        Add the following to your `settings.py` module:
+        
+        ```python
+        INSTALLED_APPS = (
+            ...  # Make sure to include the default installed apps here.
+            'rest_framework',
+        )
+        
+        REST_FRAMEWORK = {
+            # Use Django's standard `django.contrib.auth` permissions,
+            # or allow read-only access for unauthenticated users.
+            'DEFAULT_PERMISSION_CLASSES': [
+                'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
+            ]
+        }
+        ```
+        
+        That's it, we're done!
+        
+            ./manage.py runserver
+        
+        You can now open the API in your browser at `http://127.0.0.1:8000/`, and view your new 'users' API. If you use the `Login` control in the top right corner you'll also be able to add, create and delete users from the system.
+        
+        You can also interact with the API using command line tools such as [`curl`](https://curl.haxx.se/). For example, to list the users endpoint:
+        
+            $ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
+            [
+                {
+                    "url": "http://127.0.0.1:8000/users/1/",
+                    "username": "admin",
+                    "email": "admin@example.com",
+                    "is_staff": true,
+                }
+            ]
+        
+        Or to create a new user:
+        
+            $ curl -X POST -d username=new -d email=new@example.com -d is_staff=false -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
+            {
+                "url": "http://127.0.0.1:8000/users/2/",
+                "username": "new",
+                "email": "new@example.com",
+                "is_staff": false,
+            }
+        
+        # Documentation & Support
+        
+        Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
+        
+        For questions and support, use the [REST framework discussion group][group], or `#restframework` on freenode IRC.
+        
+        You may also want to [follow the author on Twitter][twitter].
+        
+        # Security
+        
+        If you believe you've found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
+        
+        Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail].  The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
+        
+        [build-status-image]: https://secure.travis-ci.org/encode/django-rest-framework.svg?branch=master
+        [travis]: https://travis-ci.org/encode/django-rest-framework?branch=master
+        [coverage-status-image]: https://img.shields.io/codecov/c/github/encode/django-rest-framework/master.svg
+        [codecov]: https://codecov.io/github/encode/django-rest-framework?branch=master
+        [pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
+        [pypi]: https://pypi.org/project/djangorestframework/
+        [twitter]: https://twitter.com/_tomchristie
+        [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
+        [sandbox]: https://restframework.herokuapp.com/
+        
+        [funding]: https://fund.django-rest-framework.org/topics/funding/
+        [sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
+        
+        [rover-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rover-readme.png
+        [sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png
+        [stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png
+        [rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png
+        [cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png
+        [load-impact-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/load-impact-readme.png
+        [kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png
+        [release-history-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/release-history.png
+        [lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png
+        
+        [rover-url]: http://jobs.rover.com/
+        [sentry-url]: https://getsentry.com/welcome/
+        [stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
+        [rollbar-url]: https://rollbar.com/
+        [cadre-url]: https://cadre.com/
+        [load-impact-url]: https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf
+        [kloudless-url]: https://hubs.ly/H0f30Lf0
+        [release-history-url]: https://releasehistory.io
+        [lightson-url]: https://lightsonsoftware.com
+        
+        [oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
+        [oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
+        [serializer-section]: https://www.django-rest-framework.org/api-guide/serializers/#serializers
+        [modelserializer-section]: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
+        [functionview-section]: https://www.django-rest-framework.org/api-guide/views/#function-based-views
+        [generic-views]: https://www.django-rest-framework.org/api-guide/generic-views/
+        [viewsets]: https://www.django-rest-framework.org/api-guide/viewsets/
+        [routers]: https://www.django-rest-framework.org/api-guide/routers/
+        [serializers]: https://www.django-rest-framework.org/api-guide/serializers/
+        [authentication]: https://www.django-rest-framework.org/api-guide/authentication/
+        [image]: https://www.django-rest-framework.org/img/quickstart.png
+        
+        [docs]: https://www.django-rest-framework.org/
+        [security-mail]: mailto:rest-framework-security@googlegroups.com
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Framework :: Django
+Classifier: Framework :: Django :: 1.11
+Classifier: Framework :: Django :: 2.0
+Classifier: Framework :: Django :: 2.1
+Classifier: Framework :: Django :: 2.2
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Internet :: WWW/HTTP
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Description-Content-Type: text/markdown

+ 228 - 0
desktop/core/ext-py/djangorestframework-3.9.4/README.md

@@ -0,0 +1,228 @@
+# [Django REST framework][docs]
+
+[![build-status-image]][travis]
+[![coverage-status-image]][codecov]
+[![pypi-version]][pypi]
+
+**Awesome web-browsable Web APIs.**
+
+Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
+
+---
+
+# Funding
+
+REST framework is a *collaboratively funded project*. If you use
+REST framework commercially we strongly encourage you to invest in its
+continued development by [signing up for a paid plan][funding].
+
+The initial aim is to provide a single full-time position on REST framework.
+*Every single sign-up makes a significant impact towards making that possible.*
+
+[![][sentry-img]][sentry-url]
+[![][stream-img]][stream-url]
+[![][rollbar-img]][rollbar-url]
+[![][cadre-img]][cadre-url]
+[![][kloudless-img]][kloudless-url]
+[![][release-history-img]][release-history-url]
+[![][lightson-img]][lightson-url]
+
+Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [Release History][release-history-url], and [Lights On Software][lightson-url].
+
+---
+
+# Overview
+
+Django REST framework is a powerful and flexible toolkit for building Web APIs.
+
+Some reasons you might want to use REST framework:
+
+* The [Web browsable API][sandbox] is a huge usability win for your developers.
+* [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
+* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
+* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
+* [Extensive documentation][docs], and [great community support][group].
+
+There is a live example API for testing purposes, [available here][sandbox].
+
+**Below**: *Screenshot from the browsable API*
+
+![Screenshot][image]
+
+----
+
+# Requirements
+
+* Python (2.7, 3.4, 3.5, 3.6, 3.7)
+* Django (1.11, 2.0, 2.1, 2.2)
+
+We **highly recommend** and only officially support the latest patch release of
+each Python and Django series.
+
+# Installation
+
+Install using `pip`...
+
+    pip install djangorestframework
+
+Add `'rest_framework'` to your `INSTALLED_APPS` setting.
+
+    INSTALLED_APPS = (
+        ...
+        'rest_framework',
+    )
+
+# Example
+
+Let's take a look at a quick example of using REST framework to build a simple model-backed API for accessing users and groups.
+
+Startup up a new project like so...
+
+    pip install django
+    pip install djangorestframework
+    django-admin startproject example .
+    ./manage.py migrate
+    ./manage.py createsuperuser
+
+
+Now edit the `example/urls.py` module in your project:
+
+```python
+from django.conf.urls import url, include
+from django.contrib.auth.models import User
+from rest_framework import serializers, viewsets, routers
+
+# Serializers define the API representation.
+class UserSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = User
+        fields = ('url', 'username', 'email', 'is_staff')
+
+
+# ViewSets define the view behavior.
+class UserViewSet(viewsets.ModelViewSet):
+    queryset = User.objects.all()
+    serializer_class = UserSerializer
+
+
+# Routers provide a way of automatically determining the URL conf.
+router = routers.DefaultRouter()
+router.register(r'users', UserViewSet)
+
+
+# Wire up our API using automatic URL routing.
+# Additionally, we include login URLs for the browsable API.
+urlpatterns = [
+    url(r'^', include(router.urls)),
+    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
+]
+```
+
+We'd also like to configure a couple of settings for our API.
+
+Add the following to your `settings.py` module:
+
+```python
+INSTALLED_APPS = (
+    ...  # Make sure to include the default installed apps here.
+    'rest_framework',
+)
+
+REST_FRAMEWORK = {
+    # Use Django's standard `django.contrib.auth` permissions,
+    # or allow read-only access for unauthenticated users.
+    'DEFAULT_PERMISSION_CLASSES': [
+        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
+    ]
+}
+```
+
+That's it, we're done!
+
+    ./manage.py runserver
+
+You can now open the API in your browser at `http://127.0.0.1:8000/`, and view your new 'users' API. If you use the `Login` control in the top right corner you'll also be able to add, create and delete users from the system.
+
+You can also interact with the API using command line tools such as [`curl`](https://curl.haxx.se/). For example, to list the users endpoint:
+
+    $ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
+    [
+        {
+            "url": "http://127.0.0.1:8000/users/1/",
+            "username": "admin",
+            "email": "admin@example.com",
+            "is_staff": true,
+        }
+    ]
+
+Or to create a new user:
+
+    $ curl -X POST -d username=new -d email=new@example.com -d is_staff=false -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
+    {
+        "url": "http://127.0.0.1:8000/users/2/",
+        "username": "new",
+        "email": "new@example.com",
+        "is_staff": false,
+    }
+
+# Documentation & Support
+
+Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
+
+For questions and support, use the [REST framework discussion group][group], or `#restframework` on freenode IRC.
+
+You may also want to [follow the author on Twitter][twitter].
+
+# Security
+
+If you believe you've found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
+
+Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail].  The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
+
+[build-status-image]: https://secure.travis-ci.org/encode/django-rest-framework.svg?branch=master
+[travis]: https://travis-ci.org/encode/django-rest-framework?branch=master
+[coverage-status-image]: https://img.shields.io/codecov/c/github/encode/django-rest-framework/master.svg
+[codecov]: https://codecov.io/github/encode/django-rest-framework?branch=master
+[pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
+[pypi]: https://pypi.org/project/djangorestframework/
+[twitter]: https://twitter.com/_tomchristie
+[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
+[sandbox]: https://restframework.herokuapp.com/
+
+[funding]: https://fund.django-rest-framework.org/topics/funding/
+[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
+
+[rover-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rover-readme.png
+[sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png
+[stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png
+[rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png
+[cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png
+[load-impact-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/load-impact-readme.png
+[kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png
+[release-history-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/release-history.png
+[lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png
+
+[rover-url]: http://jobs.rover.com/
+[sentry-url]: https://getsentry.com/welcome/
+[stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
+[rollbar-url]: https://rollbar.com/
+[cadre-url]: https://cadre.com/
+[load-impact-url]: https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf
+[kloudless-url]: https://hubs.ly/H0f30Lf0
+[release-history-url]: https://releasehistory.io
+[lightson-url]: https://lightsonsoftware.com
+
+[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
+[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
+[serializer-section]: https://www.django-rest-framework.org/api-guide/serializers/#serializers
+[modelserializer-section]: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
+[functionview-section]: https://www.django-rest-framework.org/api-guide/views/#function-based-views
+[generic-views]: https://www.django-rest-framework.org/api-guide/generic-views/
+[viewsets]: https://www.django-rest-framework.org/api-guide/viewsets/
+[routers]: https://www.django-rest-framework.org/api-guide/routers/
+[serializers]: https://www.django-rest-framework.org/api-guide/serializers/
+[authentication]: https://www.django-rest-framework.org/api-guide/authentication/
+[image]: https://www.django-rest-framework.org/img/quickstart.png
+
+[docs]: https://www.django-rest-framework.org/
+[security-mail]: mailto:rest-framework-security@googlegroups.com

+ 33 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/__init__.py

@@ -0,0 +1,33 @@
+r"""
+______ _____ _____ _____    __
+| ___ \  ___/  ___|_   _|  / _|                                           | |
+| |_/ / |__ \ `--.  | |   | |_ _ __ __ _ _ __ ___   _____      _____  _ __| |__
+|    /|  __| `--. \ | |   |  _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ /
+| |\ \| |___/\__/ / | |   | | | | | (_| | | | | | |  __/\ V  V / (_) | |  |   <
+\_| \_\____/\____/  \_/   |_| |_|  \__,_|_| |_| |_|\___| \_/\_/ \___/|_|  |_|\_|
+"""
+
+__title__ = 'Django REST framework'
+__version__ = '3.9.4'
+__author__ = 'Tom Christie'
+__license__ = 'BSD 2-Clause'
+__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
+
+# Version synonym
+VERSION = __version__
+
+# Header encoding (see RFC5987)
+HTTP_HEADER_ENCODING = 'iso-8859-1'
+
+# Default datetime input and output formats
+ISO_8601 = 'iso-8601'
+
+default_app_config = 'rest_framework.apps.RestFrameworkConfig'
+
+
+class RemovedInDRF310Warning(DeprecationWarning):
+    pass
+
+
+class RemovedInDRF311Warning(PendingDeprecationWarning):
+    pass

+ 10 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/apps.py

@@ -0,0 +1,10 @@
+from django.apps import AppConfig
+
+
+class RestFrameworkConfig(AppConfig):
+    name = 'rest_framework'
+    verbose_name = "Django REST framework"
+
+    def ready(self):
+        # Add System checks
+        from .checks import pagination_system_check  # NOQA

+ 228 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authentication.py

@@ -0,0 +1,228 @@
+"""
+Provides various authentication policies.
+"""
+from __future__ import unicode_literals
+
+import base64
+import binascii
+
+from django.contrib.auth import authenticate, get_user_model
+from django.middleware.csrf import CsrfViewMiddleware
+from django.utils.six import text_type
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework import HTTP_HEADER_ENCODING, exceptions
+
+
+def get_authorization_header(request):
+    """
+    Return request's 'Authorization:' header, as a bytestring.
+
+    Hide some test client ickyness where the header can be unicode.
+    """
+    auth = request.META.get('HTTP_AUTHORIZATION', b'')
+    if isinstance(auth, text_type):
+        # Work around django test client oddness
+        auth = auth.encode(HTTP_HEADER_ENCODING)
+    return auth
+
+
+class CSRFCheck(CsrfViewMiddleware):
+    def _reject(self, request, reason):
+        # Return the failure reason instead of an HttpResponse
+        return reason
+
+
+class BaseAuthentication(object):
+    """
+    All authentication classes should extend BaseAuthentication.
+    """
+
+    def authenticate(self, request):
+        """
+        Authenticate the request and return a two-tuple of (user, token).
+        """
+        raise NotImplementedError(".authenticate() must be overridden.")
+
+    def authenticate_header(self, request):
+        """
+        Return a string to be used as the value of the `WWW-Authenticate`
+        header in a `401 Unauthenticated` response, or `None` if the
+        authentication scheme should return `403 Permission Denied` responses.
+        """
+        pass
+
+
+class BasicAuthentication(BaseAuthentication):
+    """
+    HTTP Basic authentication against username/password.
+    """
+    www_authenticate_realm = 'api'
+
+    def authenticate(self, request):
+        """
+        Returns a `User` if a correct username and password have been supplied
+        using HTTP Basic authentication.  Otherwise returns `None`.
+        """
+        auth = get_authorization_header(request).split()
+
+        if not auth or auth[0].lower() != b'basic':
+            return None
+
+        if len(auth) == 1:
+            msg = _('Invalid basic header. No credentials provided.')
+            raise exceptions.AuthenticationFailed(msg)
+        elif len(auth) > 2:
+            msg = _('Invalid basic header. Credentials string should not contain spaces.')
+            raise exceptions.AuthenticationFailed(msg)
+
+        try:
+            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
+        except (TypeError, UnicodeDecodeError, binascii.Error):
+            msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
+            raise exceptions.AuthenticationFailed(msg)
+
+        userid, password = auth_parts[0], auth_parts[2]
+        return self.authenticate_credentials(userid, password, request)
+
+    def authenticate_credentials(self, userid, password, request=None):
+        """
+        Authenticate the userid and password against username and password
+        with optional request for context.
+        """
+        credentials = {
+            get_user_model().USERNAME_FIELD: userid,
+            'password': password
+        }
+        user = authenticate(request=request, **credentials)
+
+        if user is None:
+            raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
+
+        if not user.is_active:
+            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
+
+        return (user, None)
+
+    def authenticate_header(self, request):
+        return 'Basic realm="%s"' % self.www_authenticate_realm
+
+
+class SessionAuthentication(BaseAuthentication):
+    """
+    Use Django's session framework for authentication.
+    """
+
+    def authenticate(self, request):
+        """
+        Returns a `User` if the request session currently has a logged in user.
+        Otherwise returns `None`.
+        """
+
+        # Get the session-based user from the underlying HttpRequest object
+        user = getattr(request._request, 'user', None)
+
+        # Unauthenticated, CSRF validation not required
+        if not user or not user.is_active:
+            return None
+
+        self.enforce_csrf(request)
+
+        # CSRF passed with authenticated user
+        return (user, None)
+
+    def enforce_csrf(self, request):
+        """
+        Enforce CSRF validation for session based authentication.
+        """
+        check = CSRFCheck()
+        # populates request.META['CSRF_COOKIE'], which is used in process_view()
+        check.process_request(request)
+        reason = check.process_view(request, None, (), {})
+        if reason:
+            # CSRF failed, bail with explicit error message
+            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
+
+
+class TokenAuthentication(BaseAuthentication):
+    """
+    Simple token based authentication.
+
+    Clients should authenticate by passing the token key in the "Authorization"
+    HTTP header, prepended with the string "Token ".  For example:
+
+        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
+    """
+
+    keyword = 'Token'
+    model = None
+
+    def get_model(self):
+        if self.model is not None:
+            return self.model
+        from rest_framework.authtoken.models import Token
+        return Token
+
+    """
+    A custom token model may be used, but must have the following properties.
+
+    * key -- The string identifying the token
+    * user -- The user to which the token belongs
+    """
+
+    def authenticate(self, request):
+        auth = get_authorization_header(request).split()
+
+        if not auth or auth[0].lower() != self.keyword.lower().encode():
+            return None
+
+        if len(auth) == 1:
+            msg = _('Invalid token header. No credentials provided.')
+            raise exceptions.AuthenticationFailed(msg)
+        elif len(auth) > 2:
+            msg = _('Invalid token header. Token string should not contain spaces.')
+            raise exceptions.AuthenticationFailed(msg)
+
+        try:
+            token = auth[1].decode()
+        except UnicodeError:
+            msg = _('Invalid token header. Token string should not contain invalid characters.')
+            raise exceptions.AuthenticationFailed(msg)
+
+        return self.authenticate_credentials(token)
+
+    def authenticate_credentials(self, key):
+        model = self.get_model()
+        try:
+            token = model.objects.select_related('user').get(key=key)
+        except model.DoesNotExist:
+            raise exceptions.AuthenticationFailed(_('Invalid token.'))
+
+        if not token.user.is_active:
+            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
+
+        return (token.user, token)
+
+    def authenticate_header(self, request):
+        return self.keyword
+
+
+class RemoteUserAuthentication(BaseAuthentication):
+    """
+    REMOTE_USER authentication.
+
+    To use this, set up your web server to perform authentication, which will
+    set the REMOTE_USER environment variable. You will need to have
+    'django.contrib.auth.backends.RemoteUserBackend in your
+    AUTHENTICATION_BACKENDS setting
+    """
+
+    # Name of request header to grab username from.  This will be the key as
+    # used in the request.META dictionary, i.e. the normalization of headers to
+    # all uppercase and the addition of "HTTP_" prefix apply.
+    header = "REMOTE_USER"
+
+    def authenticate(self, request):
+        user = authenticate(remote_user=request.META.get(self.header))
+        if user and user.is_active:
+            return (user, None)

+ 1 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'

+ 12 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/admin.py

@@ -0,0 +1,12 @@
+from django.contrib import admin
+
+from rest_framework.authtoken.models import Token
+
+
+class TokenAdmin(admin.ModelAdmin):
+    list_display = ('key', 'user', 'created')
+    fields = ('user',)
+    ordering = ('-created',)
+
+
+admin.site.register(Token, TokenAdmin)

+ 7 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/apps.py

@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+from django.utils.translation import ugettext_lazy as _
+
+
+class AuthTokenConfig(AppConfig):
+    name = 'rest_framework.authtoken'
+    verbose_name = _("Auth Token")

+ 0 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/management/__init__.py


+ 0 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/management/commands/__init__.py


+ 45 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/management/commands/drf_create_token.py

@@ -0,0 +1,45 @@
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand, CommandError
+
+from rest_framework.authtoken.models import Token
+
+UserModel = get_user_model()
+
+
+class Command(BaseCommand):
+    help = 'Create DRF Token for a given user'
+
+    def create_user_token(self, username, reset_token):
+        user = UserModel._default_manager.get_by_natural_key(username)
+
+        if reset_token:
+            Token.objects.filter(user=user).delete()
+
+        token = Token.objects.get_or_create(user=user)
+        return token[0]
+
+    def add_arguments(self, parser):
+        parser.add_argument('username', type=str)
+
+        parser.add_argument(
+            '-r',
+            '--reset',
+            action='store_true',
+            dest='reset_token',
+            default=False,
+            help='Reset existing User token and create a new one',
+        )
+
+    def handle(self, *args, **options):
+        username = options['username']
+        reset_token = options['reset_token']
+
+        try:
+            token = self.create_user_token(username, reset_token)
+        except UserModel.DoesNotExist:
+            raise CommandError(
+                'Cannot create the Token: user {0} does not exist'.format(
+                    username)
+            )
+        self.stdout.write(
+            'Generated token {0} for user {1}'.format(token.key, username))

+ 26 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/migrations/0001_initial.py

@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Token',
+            fields=[
+                ('key', models.CharField(primary_key=True, serialize=False, max_length=40)),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token', on_delete=models.CASCADE)),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+    ]

+ 34 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py

@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('authtoken', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='token',
+            options={'verbose_name_plural': 'Tokens', 'verbose_name': 'Token'},
+        ),
+        migrations.AlterField(
+            model_name='token',
+            name='created',
+            field=models.DateTimeField(verbose_name='Created', auto_now_add=True),
+        ),
+        migrations.AlterField(
+            model_name='token',
+            name='key',
+            field=models.CharField(verbose_name='Key', max_length=40, primary_key=True, serialize=False),
+        ),
+        migrations.AlterField(
+            model_name='token',
+            name='user',
+            field=models.OneToOneField(to=settings.AUTH_USER_MODEL, verbose_name='User', related_name='auth_token', on_delete=models.CASCADE),
+        ),
+    ]

+ 0 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/migrations/__init__.py


+ 41 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/models.py

@@ -0,0 +1,41 @@
+import binascii
+import os
+
+from django.conf import settings
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+from django.utils.translation import ugettext_lazy as _
+
+
+@python_2_unicode_compatible
+class Token(models.Model):
+    """
+    The default authorization token model.
+    """
+    key = models.CharField(_("Key"), max_length=40, primary_key=True)
+    user = models.OneToOneField(
+        settings.AUTH_USER_MODEL, related_name='auth_token',
+        on_delete=models.CASCADE, verbose_name=_("User")
+    )
+    created = models.DateTimeField(_("Created"), auto_now_add=True)
+
+    class Meta:
+        # Work around for a bug in Django:
+        # https://code.djangoproject.com/ticket/19422
+        #
+        # Also see corresponding ticket:
+        # https://github.com/encode/django-rest-framework/issues/705
+        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
+        verbose_name = _("Token")
+        verbose_name_plural = _("Tokens")
+
+    def save(self, *args, **kwargs):
+        if not self.key:
+            self.key = self.generate_key()
+        return super(Token, self).save(*args, **kwargs)
+
+    def generate_key(self):
+        return binascii.hexlify(os.urandom(20)).decode()
+
+    def __str__(self):
+        return self.key

+ 34 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/serializers.py

@@ -0,0 +1,34 @@
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework import serializers
+
+
+class AuthTokenSerializer(serializers.Serializer):
+    username = serializers.CharField(label=_("Username"))
+    password = serializers.CharField(
+        label=_("Password"),
+        style={'input_type': 'password'},
+        trim_whitespace=False
+    )
+
+    def validate(self, attrs):
+        username = attrs.get('username')
+        password = attrs.get('password')
+
+        if username and password:
+            user = authenticate(request=self.context.get('request'),
+                                username=username, password=password)
+
+            # The authenticate call simply returns None for is_active=False
+            # users. (Assuming the default ModelBackend authentication
+            # backend.)
+            if not user:
+                msg = _('Unable to log in with provided credentials.')
+                raise serializers.ValidationError(msg, code='authorization')
+        else:
+            msg = _('Must include "username" and "password".')
+            raise serializers.ValidationError(msg, code='authorization')
+
+        attrs['user'] = user
+        return attrs

+ 50 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/authtoken/views.py

@@ -0,0 +1,50 @@
+from rest_framework import parsers, renderers
+from rest_framework.authtoken.models import Token
+from rest_framework.authtoken.serializers import AuthTokenSerializer
+from rest_framework.compat import coreapi, coreschema
+from rest_framework.response import Response
+from rest_framework.schemas import ManualSchema
+from rest_framework.views import APIView
+
+
+class ObtainAuthToken(APIView):
+    throttle_classes = ()
+    permission_classes = ()
+    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
+    renderer_classes = (renderers.JSONRenderer,)
+    serializer_class = AuthTokenSerializer
+    if coreapi is not None and coreschema is not None:
+        schema = ManualSchema(
+            fields=[
+                coreapi.Field(
+                    name="username",
+                    required=True,
+                    location='form',
+                    schema=coreschema.String(
+                        title="Username",
+                        description="Valid username for authentication",
+                    ),
+                ),
+                coreapi.Field(
+                    name="password",
+                    required=True,
+                    location='form',
+                    schema=coreschema.String(
+                        title="Password",
+                        description="Valid password for authentication",
+                    ),
+                ),
+            ],
+            encoding="application/json",
+        )
+
+    def post(self, request, *args, **kwargs):
+        serializer = self.serializer_class(data=request.data,
+                                           context={'request': request})
+        serializer.is_valid(raise_exception=True)
+        user = serializer.validated_data['user']
+        token, created = Token.objects.get_or_create(user=user)
+        return Response({'token': token.key})
+
+
+obtain_auth_token = ObtainAuthToken.as_view()

+ 21 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/checks.py

@@ -0,0 +1,21 @@
+from django.core.checks import Tags, Warning, register
+
+
+@register(Tags.compatibility)
+def pagination_system_check(app_configs, **kwargs):
+    errors = []
+    # Use of default page size setting requires a default Paginator class
+    from rest_framework.settings import api_settings
+    if api_settings.PAGE_SIZE and not api_settings.DEFAULT_PAGINATION_CLASS:
+        errors.append(
+            Warning(
+                "You have specified a default PAGE_SIZE pagination rest_framework setting,"
+                "without specifying also a DEFAULT_PAGINATION_CLASS.",
+                hint="The default for DEFAULT_PAGINATION_CLASS is None. "
+                     "In previous versions this was PageNumberPagination. "
+                     "If you wish to define PAGE_SIZE globally whilst defining "
+                     "pagination_class on a per-view basis you may silence this check.",
+                id="rest_framework.W001"
+            )
+        )
+    return errors

+ 332 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/compat.py

@@ -0,0 +1,332 @@
+"""
+The `compat` module provides support for backwards compatibility with older
+versions of Django/Python, and compatibility wrappers around optional packages.
+"""
+
+from __future__ import unicode_literals
+
+import sys
+
+from django.conf import settings
+from django.core import validators
+from django.utils import six
+from django.views.generic import View
+
+try:
+    # Python 3
+    from collections.abc import Mapping, MutableMapping   # noqa
+except ImportError:
+    # Python 2.7
+    from collections import Mapping, MutableMapping   # noqa
+
+try:
+    from django.urls import (  # noqa
+        URLPattern,
+        URLResolver,
+    )
+except ImportError:
+    # Will be removed in Django 2.0
+    from django.urls import (  # noqa
+        RegexURLPattern as URLPattern,
+        RegexURLResolver as URLResolver,
+    )
+
+try:
+    from django.core.validators import ProhibitNullCharactersValidator  # noqa
+except ImportError:
+    ProhibitNullCharactersValidator = None
+
+try:
+    from unittest import mock
+except ImportError:
+    mock = None
+
+
+def get_original_route(urlpattern):
+    """
+    Get the original route/regex that was typed in by the user into the path(), re_path() or url() directive. This
+    is in contrast with get_regex_pattern below, which for RoutePattern returns the raw regex generated from the path().
+    """
+    if hasattr(urlpattern, 'pattern'):
+        # Django 2.0
+        return str(urlpattern.pattern)
+    else:
+        # Django < 2.0
+        return urlpattern.regex.pattern
+
+
+def get_regex_pattern(urlpattern):
+    """
+    Get the raw regex out of the urlpattern's RegexPattern or RoutePattern. This is always a regular expression,
+    unlike get_original_route above.
+    """
+    if hasattr(urlpattern, 'pattern'):
+        # Django 2.0
+        return urlpattern.pattern.regex.pattern
+    else:
+        # Django < 2.0
+        return urlpattern.regex.pattern
+
+
+def is_route_pattern(urlpattern):
+    if hasattr(urlpattern, 'pattern'):
+        # Django 2.0
+        from django.urls.resolvers import RoutePattern
+        return isinstance(urlpattern.pattern, RoutePattern)
+    else:
+        # Django < 2.0
+        return False
+
+
+def make_url_resolver(regex, urlpatterns):
+    try:
+        # Django 2.0
+        from django.urls.resolvers import RegexPattern
+        return URLResolver(RegexPattern(regex), urlpatterns)
+
+    except ImportError:
+        # Django < 2.0
+        return URLResolver(regex, urlpatterns)
+
+
+def unicode_repr(instance):
+    # Get the repr of an instance, but ensure it is a unicode string
+    # on both python 3 (already the case) and 2 (not the case).
+    if six.PY2:
+        return repr(instance).decode('utf-8')
+    return repr(instance)
+
+
+def unicode_to_repr(value):
+    # Coerce a unicode string to the correct repr return type, depending on
+    # the Python version. We wrap all our `__repr__` implementations with
+    # this and then use unicode throughout internally.
+    if six.PY2:
+        return value.encode('utf-8')
+    return value
+
+
+def unicode_http_header(value):
+    # Coerce HTTP header value to unicode.
+    if isinstance(value, bytes):
+        return value.decode('iso-8859-1')
+    return value
+
+
+def distinct(queryset, base):
+    if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle":
+        # distinct analogue for Oracle users
+        return base.filter(pk__in=set(queryset.values_list('pk', flat=True)))
+    return queryset.distinct()
+
+
+# django.contrib.postgres requires psycopg2
+try:
+    from django.contrib.postgres import fields as postgres_fields
+except ImportError:
+    postgres_fields = None
+
+
+# coreapi is optional (Note that uritemplate is a dependency of coreapi)
+try:
+    import coreapi
+    import uritemplate
+except ImportError:
+    coreapi = None
+    uritemplate = None
+
+
+# coreschema is optional
+try:
+    import coreschema
+except ImportError:
+    coreschema = None
+
+
+# pyyaml is optional
+try:
+    import yaml
+except ImportError:
+    yaml = None
+
+
+# django-crispy-forms is optional
+try:
+    import crispy_forms
+except ImportError:
+    crispy_forms = None
+
+
+# requests is optional
+try:
+    import requests
+except ImportError:
+    requests = None
+
+
+def is_guardian_installed():
+    """
+    django-guardian is optional and only imported if in INSTALLED_APPS.
+    """
+    try:
+        import guardian
+    except ImportError:
+        guardian = None
+
+    if six.PY2 and (not guardian or guardian.VERSION >= (1, 5)):
+        # Guardian 1.5.0, for Django 2.2 is NOT compatible with Python 2.7.
+        # Remove when dropping PY2.
+        return False
+    return 'guardian' in settings.INSTALLED_APPS
+
+
+# PATCH method is not implemented by Django
+if 'patch' not in View.http_method_names:
+    View.http_method_names = View.http_method_names + ['patch']
+
+
+# Markdown is optional
+try:
+    import markdown
+
+    if markdown.version <= '2.2':
+        HEADERID_EXT_PATH = 'headerid'
+        LEVEL_PARAM = 'level'
+    elif markdown.version < '2.6':
+        HEADERID_EXT_PATH = 'markdown.extensions.headerid'
+        LEVEL_PARAM = 'level'
+    else:
+        HEADERID_EXT_PATH = 'markdown.extensions.toc'
+        LEVEL_PARAM = 'baselevel'
+
+    def apply_markdown(text):
+        """
+        Simple wrapper around :func:`markdown.markdown` to set the base level
+        of '#' style headers to <h2>.
+        """
+        extensions = [HEADERID_EXT_PATH]
+        extension_configs = {
+            HEADERID_EXT_PATH: {
+                LEVEL_PARAM: '2'
+            }
+        }
+        md = markdown.Markdown(
+            extensions=extensions, extension_configs=extension_configs
+        )
+        md_filter_add_syntax_highlight(md)
+        return md.convert(text)
+except ImportError:
+    apply_markdown = None
+    markdown = None
+
+
+try:
+    import pygments
+    from pygments.lexers import get_lexer_by_name, TextLexer
+    from pygments.formatters import HtmlFormatter
+
+    def pygments_highlight(text, lang, style):
+        lexer = get_lexer_by_name(lang, stripall=False)
+        formatter = HtmlFormatter(nowrap=True, style=style)
+        return pygments.highlight(text, lexer, formatter)
+
+    def pygments_css(style):
+        formatter = HtmlFormatter(style=style)
+        return formatter.get_style_defs('.highlight')
+
+except ImportError:
+    pygments = None
+
+    def pygments_highlight(text, lang, style):
+        return text
+
+    def pygments_css(style):
+        return None
+
+if markdown is not None and pygments is not None:
+    # starting from this blogpost and modified to support current markdown extensions API
+    # https://zerokspot.com/weblog/2008/06/18/syntax-highlighting-in-markdown-with-pygments/
+
+    from markdown.preprocessors import Preprocessor
+    import re
+
+    class CodeBlockPreprocessor(Preprocessor):
+        pattern = re.compile(
+            r'^\s*``` *([^\n]+)\n(.+?)^\s*```', re.M | re.S)
+
+        formatter = HtmlFormatter()
+
+        def run(self, lines):
+            def repl(m):
+                try:
+                    lexer = get_lexer_by_name(m.group(1))
+                except (ValueError, NameError):
+                    lexer = TextLexer()
+                code = m.group(2).replace('\t', '    ')
+                code = pygments.highlight(code, lexer, self.formatter)
+                code = code.replace('\n\n', '\n&nbsp;\n').replace('\n', '<br />').replace('\\@', '@')
+                return '\n\n%s\n\n' % code
+            ret = self.pattern.sub(repl, "\n".join(lines))
+            return ret.split("\n")
+
+    def md_filter_add_syntax_highlight(md):
+        md.preprocessors.add('highlight', CodeBlockPreprocessor(), "_begin")
+        return True
+else:
+    def md_filter_add_syntax_highlight(md):
+        return False
+
+
+# Django 1.x url routing syntax. Remove when dropping Django 1.11 support.
+try:
+    from django.urls import include, path, re_path, register_converter  # noqa
+except ImportError:
+    from django.conf.urls import include, url # noqa
+    path = None
+    register_converter = None
+    re_path = url
+
+
+# `separators` argument to `json.dumps()` differs between 2.x and 3.x
+# See: https://bugs.python.org/issue22767
+if six.PY3:
+    SHORT_SEPARATORS = (',', ':')
+    LONG_SEPARATORS = (', ', ': ')
+    INDENT_SEPARATORS = (',', ': ')
+else:
+    SHORT_SEPARATORS = (b',', b':')
+    LONG_SEPARATORS = (b', ', b': ')
+    INDENT_SEPARATORS = (b',', b': ')
+
+
+class CustomValidatorMessage(object):
+    """
+    We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`.
+    https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297
+
+    Ref: https://github.com/encode/django-rest-framework/pull/5452
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.message = kwargs.pop('message', self.message)
+        super(CustomValidatorMessage, self).__init__(*args, **kwargs)
+
+
+class MinValueValidator(CustomValidatorMessage, validators.MinValueValidator):
+    pass
+
+
+class MaxValueValidator(CustomValidatorMessage, validators.MaxValueValidator):
+    pass
+
+
+class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
+    pass
+
+
+class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
+    pass
+
+
+# Version Constants.
+PY36 = sys.version_info >= (3, 6)

+ 255 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/decorators.py

@@ -0,0 +1,255 @@
+"""
+The most important decorator in this module is `@api_view`, which is used
+for writing function-based views with REST framework.
+
+There are also various decorators for setting the API policies on function
+based views, as well as the `@detail_route` and `@list_route` decorators, which are
+used to annotate methods on viewsets that should be included by routers.
+"""
+from __future__ import unicode_literals
+
+import types
+import warnings
+
+from django.forms.utils import pretty_name
+from django.utils import six
+
+from rest_framework import RemovedInDRF310Warning
+from rest_framework.views import APIView
+
+
+def api_view(http_method_names=None):
+    """
+    Decorator that converts a function-based view into an APIView subclass.
+    Takes a list of allowed methods for the view as an argument.
+    """
+    http_method_names = ['GET'] if (http_method_names is None) else http_method_names
+
+    def decorator(func):
+
+        WrappedAPIView = type(
+            six.PY3 and 'WrappedAPIView' or b'WrappedAPIView',
+            (APIView,),
+            {'__doc__': func.__doc__}
+        )
+
+        # Note, the above allows us to set the docstring.
+        # It is the equivalent of:
+        #
+        #     class WrappedAPIView(APIView):
+        #         pass
+        #     WrappedAPIView.__doc__ = func.doc    <--- Not possible to do this
+
+        # api_view applied without (method_names)
+        assert not(isinstance(http_method_names, types.FunctionType)), \
+            '@api_view missing list of allowed HTTP methods'
+
+        # api_view applied with eg. string instead of list of strings
+        assert isinstance(http_method_names, (list, tuple)), \
+            '@api_view expected a list of strings, received %s' % type(http_method_names).__name__
+
+        allowed_methods = set(http_method_names) | {'options'}
+        WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
+
+        def handler(self, *args, **kwargs):
+            return func(*args, **kwargs)
+
+        for method in http_method_names:
+            setattr(WrappedAPIView, method.lower(), handler)
+
+        WrappedAPIView.__name__ = func.__name__
+        WrappedAPIView.__module__ = func.__module__
+
+        WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
+                                                  APIView.renderer_classes)
+
+        WrappedAPIView.parser_classes = getattr(func, 'parser_classes',
+                                                APIView.parser_classes)
+
+        WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes',
+                                                        APIView.authentication_classes)
+
+        WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes',
+                                                  APIView.throttle_classes)
+
+        WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
+                                                    APIView.permission_classes)
+
+        WrappedAPIView.schema = getattr(func, 'schema',
+                                        APIView.schema)
+
+        return WrappedAPIView.as_view()
+
+    return decorator
+
+
+def renderer_classes(renderer_classes):
+    def decorator(func):
+        func.renderer_classes = renderer_classes
+        return func
+    return decorator
+
+
+def parser_classes(parser_classes):
+    def decorator(func):
+        func.parser_classes = parser_classes
+        return func
+    return decorator
+
+
+def authentication_classes(authentication_classes):
+    def decorator(func):
+        func.authentication_classes = authentication_classes
+        return func
+    return decorator
+
+
+def throttle_classes(throttle_classes):
+    def decorator(func):
+        func.throttle_classes = throttle_classes
+        return func
+    return decorator
+
+
+def permission_classes(permission_classes):
+    def decorator(func):
+        func.permission_classes = permission_classes
+        return func
+    return decorator
+
+
+def schema(view_inspector):
+    def decorator(func):
+        func.schema = view_inspector
+        return func
+    return decorator
+
+
+def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
+    """
+    Mark a ViewSet method as a routable action.
+
+    Set the `detail` boolean to determine if this action should apply to
+    instance/detail requests or collection/list requests.
+    """
+    methods = ['get'] if (methods is None) else methods
+    methods = [method.lower() for method in methods]
+
+    assert detail is not None, (
+        "@action() missing required argument: 'detail'"
+    )
+
+    # name and suffix are mutually exclusive
+    if 'name' in kwargs and 'suffix' in kwargs:
+        raise TypeError("`name` and `suffix` are mutually exclusive arguments.")
+
+    def decorator(func):
+        func.mapping = MethodMapper(func, methods)
+
+        func.detail = detail
+        func.url_path = url_path if url_path else func.__name__
+        func.url_name = url_name if url_name else func.__name__.replace('_', '-')
+        func.kwargs = kwargs
+
+        # Set descriptive arguments for viewsets
+        if 'name' not in kwargs and 'suffix' not in kwargs:
+            func.kwargs['name'] = pretty_name(func.__name__)
+        func.kwargs['description'] = func.__doc__ or None
+
+        return func
+    return decorator
+
+
+class MethodMapper(dict):
+    """
+    Enables mapping HTTP methods to different ViewSet methods for a single,
+    logical action.
+
+    Example usage:
+
+        class MyViewSet(ViewSet):
+
+            @action(detail=False)
+            def example(self, request, **kwargs):
+                ...
+
+            @example.mapping.post
+            def create_example(self, request, **kwargs):
+                ...
+    """
+
+    def __init__(self, action, methods):
+        self.action = action
+        for method in methods:
+            self[method] = self.action.__name__
+
+    def _map(self, method, func):
+        assert method not in self, (
+            "Method '%s' has already been mapped to '.%s'." % (method, self[method]))
+        assert func.__name__ != self.action.__name__, (
+            "Method mapping does not behave like the property decorator. You "
+            "cannot use the same method name for each mapping declaration.")
+
+        self[method] = func.__name__
+
+        return func
+
+    def get(self, func):
+        return self._map('get', func)
+
+    def post(self, func):
+        return self._map('post', func)
+
+    def put(self, func):
+        return self._map('put', func)
+
+    def patch(self, func):
+        return self._map('patch', func)
+
+    def delete(self, func):
+        return self._map('delete', func)
+
+    def head(self, func):
+        return self._map('head', func)
+
+    def options(self, func):
+        return self._map('options', func)
+
+    def trace(self, func):
+        return self._map('trace', func)
+
+
+def detail_route(methods=None, **kwargs):
+    """
+    Used to mark a method on a ViewSet that should be routed for detail requests.
+    """
+    warnings.warn(
+        "`detail_route` is deprecated and will be removed in 3.10 in favor of "
+        "`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
+        RemovedInDRF310Warning, stacklevel=2
+    )
+
+    def decorator(func):
+        func = action(methods, detail=True, **kwargs)(func)
+        if 'url_name' not in kwargs:
+            func.url_name = func.url_path.replace('_', '-')
+        return func
+    return decorator
+
+
+def list_route(methods=None, **kwargs):
+    """
+    Used to mark a method on a ViewSet that should be routed for list requests.
+    """
+    warnings.warn(
+        "`list_route` is deprecated and will be removed in 3.10 in favor of "
+        "`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
+        RemovedInDRF310Warning, stacklevel=2
+    )
+
+    def decorator(func):
+        func = action(methods, detail=False, **kwargs)(func)
+        if 'url_name' not in kwargs:
+            func.url_name = func.url_path.replace('_', '-')
+        return func
+    return decorator

+ 84 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/documentation.py

@@ -0,0 +1,84 @@
+from django.conf.urls import include, url
+
+from rest_framework.renderers import (
+    CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer
+)
+from rest_framework.schemas import SchemaGenerator, get_schema_view
+from rest_framework.settings import api_settings
+
+
+def get_docs_view(
+        title=None, description=None, schema_url=None, public=True,
+        patterns=None, generator_class=SchemaGenerator,
+        authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
+        permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
+        renderer_classes=None):
+
+    if renderer_classes is None:
+        renderer_classes = [DocumentationRenderer, CoreJSONRenderer]
+
+    return get_schema_view(
+        title=title,
+        url=schema_url,
+        description=description,
+        renderer_classes=renderer_classes,
+        public=public,
+        patterns=patterns,
+        generator_class=generator_class,
+        authentication_classes=authentication_classes,
+        permission_classes=permission_classes,
+    )
+
+
+def get_schemajs_view(
+        title=None, description=None, schema_url=None, public=True,
+        patterns=None, generator_class=SchemaGenerator,
+        authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
+        permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
+    renderer_classes = [SchemaJSRenderer]
+
+    return get_schema_view(
+        title=title,
+        url=schema_url,
+        description=description,
+        renderer_classes=renderer_classes,
+        public=public,
+        patterns=patterns,
+        generator_class=generator_class,
+        authentication_classes=authentication_classes,
+        permission_classes=permission_classes,
+    )
+
+
+def include_docs_urls(
+        title=None, description=None, schema_url=None, public=True,
+        patterns=None, generator_class=SchemaGenerator,
+        authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
+        permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
+        renderer_classes=None):
+    docs_view = get_docs_view(
+        title=title,
+        description=description,
+        schema_url=schema_url,
+        public=public,
+        patterns=patterns,
+        generator_class=generator_class,
+        authentication_classes=authentication_classes,
+        renderer_classes=renderer_classes,
+        permission_classes=permission_classes,
+    )
+    schema_js_view = get_schemajs_view(
+        title=title,
+        description=description,
+        schema_url=schema_url,
+        public=public,
+        patterns=patterns,
+        generator_class=generator_class,
+        authentication_classes=authentication_classes,
+        permission_classes=permission_classes,
+    )
+    urls = [
+        url(r'^$', docs_view, name='docs-index'),
+        url(r'^schema.js$', schema_js_view, name='schema-js')
+    ]
+    return include((urls, 'api-docs'), namespace='api-docs')

+ 261 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/exceptions.py

@@ -0,0 +1,261 @@
+"""
+Handled exceptions raised by REST framework.
+
+In addition Django's built in 403 and 404 exceptions are handled.
+(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
+"""
+from __future__ import unicode_literals
+
+import math
+
+from django.http import JsonResponse
+from django.utils import six
+from django.utils.encoding import force_text
+from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ungettext
+
+from rest_framework import status
+from rest_framework.compat import unicode_to_repr
+from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
+
+
+def _get_error_details(data, default_code=None):
+    """
+    Descend into a nested data structure, forcing any
+    lazy translation strings or strings into `ErrorDetail`.
+    """
+    if isinstance(data, list):
+        ret = [
+            _get_error_details(item, default_code) for item in data
+        ]
+        if isinstance(data, ReturnList):
+            return ReturnList(ret, serializer=data.serializer)
+        return ret
+    elif isinstance(data, dict):
+        ret = {
+            key: _get_error_details(value, default_code)
+            for key, value in data.items()
+        }
+        if isinstance(data, ReturnDict):
+            return ReturnDict(ret, serializer=data.serializer)
+        return ret
+
+    text = force_text(data)
+    code = getattr(data, 'code', default_code)
+    return ErrorDetail(text, code)
+
+
+def _get_codes(detail):
+    if isinstance(detail, list):
+        return [_get_codes(item) for item in detail]
+    elif isinstance(detail, dict):
+        return {key: _get_codes(value) for key, value in detail.items()}
+    return detail.code
+
+
+def _get_full_details(detail):
+    if isinstance(detail, list):
+        return [_get_full_details(item) for item in detail]
+    elif isinstance(detail, dict):
+        return {key: _get_full_details(value) for key, value in detail.items()}
+    return {
+        'message': detail,
+        'code': detail.code
+    }
+
+
+class ErrorDetail(six.text_type):
+    """
+    A string-like object that can additionally have a code.
+    """
+    code = None
+
+    def __new__(cls, string, code=None):
+        self = super(ErrorDetail, cls).__new__(cls, string)
+        self.code = code
+        return self
+
+    def __eq__(self, other):
+        r = super(ErrorDetail, self).__eq__(other)
+        try:
+            return r and self.code == other.code
+        except AttributeError:
+            return r
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return unicode_to_repr('ErrorDetail(string=%r, code=%r)' % (
+            six.text_type(self),
+            self.code,
+        ))
+
+    def __hash__(self):
+        return hash(str(self))
+
+
+class APIException(Exception):
+    """
+    Base class for REST framework exceptions.
+    Subclasses should provide `.status_code` and `.default_detail` properties.
+    """
+    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
+    default_detail = _('A server error occurred.')
+    default_code = 'error'
+
+    def __init__(self, detail=None, code=None):
+        if detail is None:
+            detail = self.default_detail
+        if code is None:
+            code = self.default_code
+
+        self.detail = _get_error_details(detail, code)
+
+    def __str__(self):
+        return six.text_type(self.detail)
+
+    def get_codes(self):
+        """
+        Return only the code part of the error details.
+
+        Eg. {"name": ["required"]}
+        """
+        return _get_codes(self.detail)
+
+    def get_full_details(self):
+        """
+        Return both the message & code parts of the error details.
+
+        Eg. {"name": [{"message": "This field is required.", "code": "required"}]}
+        """
+        return _get_full_details(self.detail)
+
+
+# The recommended style for using `ValidationError` is to keep it namespaced
+# under `serializers`, in order to minimize potential confusion with Django's
+# built in `ValidationError`. For example:
+#
+# from rest_framework import serializers
+# raise serializers.ValidationError('Value was invalid')
+
+class ValidationError(APIException):
+    status_code = status.HTTP_400_BAD_REQUEST
+    default_detail = _('Invalid input.')
+    default_code = 'invalid'
+
+    def __init__(self, detail=None, code=None):
+        if detail is None:
+            detail = self.default_detail
+        if code is None:
+            code = self.default_code
+
+        # For validation failures, we may collect many errors together,
+        # so the details should always be coerced to a list if not already.
+        if not isinstance(detail, dict) and not isinstance(detail, list):
+            detail = [detail]
+
+        self.detail = _get_error_details(detail, code)
+
+
+class ParseError(APIException):
+    status_code = status.HTTP_400_BAD_REQUEST
+    default_detail = _('Malformed request.')
+    default_code = 'parse_error'
+
+
+class AuthenticationFailed(APIException):
+    status_code = status.HTTP_401_UNAUTHORIZED
+    default_detail = _('Incorrect authentication credentials.')
+    default_code = 'authentication_failed'
+
+
+class NotAuthenticated(APIException):
+    status_code = status.HTTP_401_UNAUTHORIZED
+    default_detail = _('Authentication credentials were not provided.')
+    default_code = 'not_authenticated'
+
+
+class PermissionDenied(APIException):
+    status_code = status.HTTP_403_FORBIDDEN
+    default_detail = _('You do not have permission to perform this action.')
+    default_code = 'permission_denied'
+
+
+class NotFound(APIException):
+    status_code = status.HTTP_404_NOT_FOUND
+    default_detail = _('Not found.')
+    default_code = 'not_found'
+
+
+class MethodNotAllowed(APIException):
+    status_code = status.HTTP_405_METHOD_NOT_ALLOWED
+    default_detail = _('Method "{method}" not allowed.')
+    default_code = 'method_not_allowed'
+
+    def __init__(self, method, detail=None, code=None):
+        if detail is None:
+            detail = force_text(self.default_detail).format(method=method)
+        super(MethodNotAllowed, self).__init__(detail, code)
+
+
+class NotAcceptable(APIException):
+    status_code = status.HTTP_406_NOT_ACCEPTABLE
+    default_detail = _('Could not satisfy the request Accept header.')
+    default_code = 'not_acceptable'
+
+    def __init__(self, detail=None, code=None, available_renderers=None):
+        self.available_renderers = available_renderers
+        super(NotAcceptable, self).__init__(detail, code)
+
+
+class UnsupportedMediaType(APIException):
+    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
+    default_detail = _('Unsupported media type "{media_type}" in request.')
+    default_code = 'unsupported_media_type'
+
+    def __init__(self, media_type, detail=None, code=None):
+        if detail is None:
+            detail = force_text(self.default_detail).format(media_type=media_type)
+        super(UnsupportedMediaType, self).__init__(detail, code)
+
+
+class Throttled(APIException):
+    status_code = status.HTTP_429_TOO_MANY_REQUESTS
+    default_detail = _('Request was throttled.')
+    extra_detail_singular = 'Expected available in {wait} second.'
+    extra_detail_plural = 'Expected available in {wait} seconds.'
+    default_code = 'throttled'
+
+    def __init__(self, wait=None, detail=None, code=None):
+        if detail is None:
+            detail = force_text(self.default_detail)
+        if wait is not None:
+            wait = math.ceil(wait)
+            detail = ' '.join((
+                detail,
+                force_text(ungettext(self.extra_detail_singular.format(wait=wait),
+                                     self.extra_detail_plural.format(wait=wait),
+                                     wait))))
+        self.wait = wait
+        super(Throttled, self).__init__(detail, code)
+
+
+def server_error(request, *args, **kwargs):
+    """
+    Generic 500 error handler.
+    """
+    data = {
+        'error': 'Server Error (500)'
+    }
+    return JsonResponse(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+def bad_request(request, exception, *args, **kwargs):
+    """
+    Generic 400 error handler.
+    """
+    data = {
+        'error': 'Bad Request (400)'
+    }
+    return JsonResponse(data, status=status.HTTP_400_BAD_REQUEST)

+ 1931 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/fields.py

@@ -0,0 +1,1931 @@
+from __future__ import unicode_literals
+
+import copy
+import datetime
+import decimal
+import functools
+import inspect
+import re
+import uuid
+from collections import OrderedDict
+
+from django.conf import settings
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ValidationError as DjangoValidationError
+from django.core.validators import (
+    EmailValidator, RegexValidator, URLValidator, ip_address_validators
+)
+from django.forms import FilePathField as DjangoFilePathField
+from django.forms import ImageField as DjangoImageField
+from django.utils import six, timezone
+from django.utils.dateparse import (
+    parse_date, parse_datetime, parse_duration, parse_time
+)
+from django.utils.duration import duration_string
+from django.utils.encoding import is_protected_type, smart_text
+from django.utils.formats import localize_input, sanitize_separators
+from django.utils.functional import lazy
+from django.utils.ipv6 import clean_ipv6_address
+from django.utils.timezone import utc
+from django.utils.translation import ugettext_lazy as _
+from pytz.exceptions import InvalidTimeError
+
+from rest_framework import ISO_8601
+from rest_framework.compat import (
+    Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
+    MinValueValidator, ProhibitNullCharactersValidator, unicode_repr,
+    unicode_to_repr
+)
+from rest_framework.exceptions import ErrorDetail, ValidationError
+from rest_framework.settings import api_settings
+from rest_framework.utils import html, humanize_datetime, json, representation
+
+
+class empty:
+    """
+    This class is used to represent no data being provided for a given input
+    or output value.
+
+    It is required because `None` may be a valid input or output value.
+    """
+    pass
+
+
+if six.PY3:
+    def is_simple_callable(obj):
+        """
+        True if the object is a callable that takes no arguments.
+        """
+        if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
+            return False
+
+        sig = inspect.signature(obj)
+        params = sig.parameters.values()
+        return all(
+            param.kind == param.VAR_POSITIONAL or
+            param.kind == param.VAR_KEYWORD or
+            param.default != param.empty
+            for param in params
+        )
+
+else:
+    def is_simple_callable(obj):
+        function = inspect.isfunction(obj)
+        method = inspect.ismethod(obj)
+
+        if not (function or method):
+            return False
+
+        if method:
+            is_unbound = obj.im_self is None
+
+        args, _, _, defaults = inspect.getargspec(obj)
+
+        len_args = len(args) if function or is_unbound else len(args) - 1
+        len_defaults = len(defaults) if defaults else 0
+        return len_args <= len_defaults
+
+
+def get_attribute(instance, attrs):
+    """
+    Similar to Python's built in `getattr(instance, attr)`,
+    but takes a list of nested attributes, instead of a single attribute.
+
+    Also accepts either attribute lookup on objects or dictionary lookups.
+    """
+    for attr in attrs:
+        try:
+            if isinstance(instance, Mapping):
+                instance = instance[attr]
+            else:
+                instance = getattr(instance, attr)
+        except ObjectDoesNotExist:
+            return None
+        if is_simple_callable(instance):
+            try:
+                instance = instance()
+            except (AttributeError, KeyError) as exc:
+                # If we raised an Attribute or KeyError here it'd get treated
+                # as an omitted field in `Field.get_attribute()`. Instead we
+                # raise a ValueError to ensure the exception is not masked.
+                raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))
+
+    return instance
+
+
+def set_value(dictionary, keys, value):
+    """
+    Similar to Python's built in `dictionary[key] = value`,
+    but takes a list of nested keys instead of a single key.
+
+    set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
+    set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
+    set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
+    """
+    if not keys:
+        dictionary.update(value)
+        return
+
+    for key in keys[:-1]:
+        if key not in dictionary:
+            dictionary[key] = {}
+        dictionary = dictionary[key]
+
+    dictionary[keys[-1]] = value
+
+
+def to_choices_dict(choices):
+    """
+    Convert choices into key/value dicts.
+
+    to_choices_dict([1]) -> {1: 1}
+    to_choices_dict([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
+    to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2'}}
+    """
+    # Allow single, paired or grouped choices style:
+    # choices = [1, 2, 3]
+    # choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
+    # choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
+    ret = OrderedDict()
+    for choice in choices:
+        if not isinstance(choice, (list, tuple)):
+            # single choice
+            ret[choice] = choice
+        else:
+            key, value = choice
+            if isinstance(value, (list, tuple)):
+                # grouped choices (category, sub choices)
+                ret[key] = to_choices_dict(value)
+            else:
+                # paired choice (key, display value)
+                ret[key] = value
+    return ret
+
+
+def flatten_choices_dict(choices):
+    """
+    Convert a group choices dict into a flat dict of choices.
+
+    flatten_choices_dict({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'}
+    flatten_choices_dict({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'}
+    """
+    ret = OrderedDict()
+    for key, value in choices.items():
+        if isinstance(value, dict):
+            # grouped choices (category, sub choices)
+            for sub_key, sub_value in value.items():
+                ret[sub_key] = sub_value
+        else:
+            # choice (key, display value)
+            ret[key] = value
+    return ret
+
+
+def iter_options(grouped_choices, cutoff=None, cutoff_text=None):
+    """
+    Helper function for options and option groups in templates.
+    """
+    class StartOptionGroup(object):
+        start_option_group = True
+        end_option_group = False
+
+        def __init__(self, label):
+            self.label = label
+
+    class EndOptionGroup(object):
+        start_option_group = False
+        end_option_group = True
+
+    class Option(object):
+        start_option_group = False
+        end_option_group = False
+
+        def __init__(self, value, display_text, disabled=False):
+            self.value = value
+            self.display_text = display_text
+            self.disabled = disabled
+
+    count = 0
+
+    for key, value in grouped_choices.items():
+        if cutoff and count >= cutoff:
+            break
+
+        if isinstance(value, dict):
+            yield StartOptionGroup(label=key)
+            for sub_key, sub_value in value.items():
+                if cutoff and count >= cutoff:
+                    break
+                yield Option(value=sub_key, display_text=sub_value)
+                count += 1
+            yield EndOptionGroup()
+        else:
+            yield Option(value=key, display_text=value)
+            count += 1
+
+    if cutoff and count >= cutoff and cutoff_text:
+        cutoff_text = cutoff_text.format(count=cutoff)
+        yield Option(value='n/a', display_text=cutoff_text, disabled=True)
+
+
+def get_error_detail(exc_info):
+    """
+    Given a Django ValidationError, return a list of ErrorDetail,
+    with the `code` populated.
+    """
+    code = getattr(exc_info, 'code', None) or 'invalid'
+
+    try:
+        error_dict = exc_info.error_dict
+    except AttributeError:
+        return [
+            ErrorDetail(error.message % (error.params or ()),
+                        code=error.code if error.code else code)
+            for error in exc_info.error_list]
+    return {
+        k: [
+            ErrorDetail(error.message % (error.params or ()),
+                        code=error.code if error.code else code)
+            for error in errors
+        ] for k, errors in error_dict.items()
+    }
+
+
+class CreateOnlyDefault(object):
+    """
+    This class may be used to provide default values that are only used
+    for create operations, but that do not return any value for update
+    operations.
+    """
+    def __init__(self, default):
+        self.default = default
+
+    def set_context(self, serializer_field):
+        self.is_update = serializer_field.parent.instance is not None
+        if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update:
+            self.default.set_context(serializer_field)
+
+    def __call__(self):
+        if self.is_update:
+            raise SkipField()
+        if callable(self.default):
+            return self.default()
+        return self.default
+
+    def __repr__(self):
+        return unicode_to_repr(
+            '%s(%s)' % (self.__class__.__name__, unicode_repr(self.default))
+        )
+
+
+class CurrentUserDefault(object):
+    def set_context(self, serializer_field):
+        self.user = serializer_field.context['request'].user
+
+    def __call__(self):
+        return self.user
+
+    def __repr__(self):
+        return unicode_to_repr('%s()' % self.__class__.__name__)
+
+
+class SkipField(Exception):
+    pass
+
+
+REGEX_TYPE = type(re.compile(''))
+
+NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`'
+NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`'
+NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
+USE_READONLYFIELD = 'Field(read_only=True) should be ReadOnlyField'
+MISSING_ERROR_MESSAGE = (
+    'ValidationError raised by `{class_name}`, but error key `{key}` does '
+    'not exist in the `error_messages` dictionary.'
+)
+
+
+class Field(object):
+    _creation_counter = 0
+
+    default_error_messages = {
+        'required': _('This field is required.'),
+        'null': _('This field may not be null.')
+    }
+    default_validators = []
+    default_empty_html = empty
+    initial = None
+
+    def __init__(self, read_only=False, write_only=False,
+                 required=None, default=empty, initial=empty, source=None,
+                 label=None, help_text=None, style=None,
+                 error_messages=None, validators=None, allow_null=False):
+        self._creation_counter = Field._creation_counter
+        Field._creation_counter += 1
+
+        # If `required` is unset, then use `True` unless a default is provided.
+        if required is None:
+            required = default is empty and not read_only
+
+        # Some combinations of keyword arguments do not make sense.
+        assert not (read_only and write_only), NOT_READ_ONLY_WRITE_ONLY
+        assert not (read_only and required), NOT_READ_ONLY_REQUIRED
+        assert not (required and default is not empty), NOT_REQUIRED_DEFAULT
+        assert not (read_only and self.__class__ == Field), USE_READONLYFIELD
+
+        self.read_only = read_only
+        self.write_only = write_only
+        self.required = required
+        self.default = default
+        self.source = source
+        self.initial = self.initial if (initial is empty) else initial
+        self.label = label
+        self.help_text = help_text
+        self.style = {} if style is None else style
+        self.allow_null = allow_null
+
+        if self.default_empty_html is not empty:
+            if default is not empty:
+                self.default_empty_html = default
+
+        if validators is not None:
+            self.validators = list(validators)
+
+        # These are set up by `.bind()` when the field is added to a serializer.
+        self.field_name = None
+        self.parent = None
+
+        # Collect default error message from self and parent classes
+        messages = {}
+        for cls in reversed(self.__class__.__mro__):
+            messages.update(getattr(cls, 'default_error_messages', {}))
+        messages.update(error_messages or {})
+        self.error_messages = messages
+
+    def bind(self, field_name, parent):
+        """
+        Initializes the field name and parent for the field instance.
+        Called when a field is added to the parent serializer instance.
+        """
+
+        # In order to enforce a consistent style, we error if a redundant
+        # 'source' argument has been used. For example:
+        # my_field = serializer.CharField(source='my_field')
+        assert self.source != field_name, (
+            "It is redundant to specify `source='%s'` on field '%s' in "
+            "serializer '%s', because it is the same as the field name. "
+            "Remove the `source` keyword argument." %
+            (field_name, self.__class__.__name__, parent.__class__.__name__)
+        )
+
+        self.field_name = field_name
+        self.parent = parent
+
+        # `self.label` should default to being based on the field name.
+        if self.label is None:
+            self.label = field_name.replace('_', ' ').capitalize()
+
+        # self.source should default to being the same as the field name.
+        if self.source is None:
+            self.source = field_name
+
+        # self.source_attrs is a list of attributes that need to be looked up
+        # when serializing the instance, or populating the validated data.
+        if self.source == '*':
+            self.source_attrs = []
+        else:
+            self.source_attrs = self.source.split('.')
+
+    # .validators is a lazily loaded property, that gets its default
+    # value from `get_validators`.
+    @property
+    def validators(self):
+        if not hasattr(self, '_validators'):
+            self._validators = self.get_validators()
+        return self._validators
+
+    @validators.setter
+    def validators(self, validators):
+        self._validators = validators
+
+    def get_validators(self):
+        return list(self.default_validators)
+
+    def get_initial(self):
+        """
+        Return a value to use when the field is being returned as a primitive
+        value, without any object instance.
+        """
+        if callable(self.initial):
+            return self.initial()
+        return self.initial
+
+    def get_value(self, dictionary):
+        """
+        Given the *incoming* primitive data, return the value for this field
+        that should be validated and transformed to a native value.
+        """
+        if html.is_html_input(dictionary):
+            # HTML forms will represent empty fields as '', and cannot
+            # represent None or False values directly.
+            if self.field_name not in dictionary:
+                if getattr(self.root, 'partial', False):
+                    return empty
+                return self.default_empty_html
+            ret = dictionary[self.field_name]
+            if ret == '' and self.allow_null:
+                # If the field is blank, and null is a valid value then
+                # determine if we should use null instead.
+                return '' if getattr(self, 'allow_blank', False) else None
+            elif ret == '' and not self.required:
+                # If the field is blank, and emptiness is valid then
+                # determine if we should use emptiness instead.
+                return '' if getattr(self, 'allow_blank', False) else empty
+            return ret
+        return dictionary.get(self.field_name, empty)
+
+    def get_attribute(self, instance):
+        """
+        Given the *outgoing* object instance, return the primitive value
+        that should be used for this field.
+        """
+        try:
+            return get_attribute(instance, self.source_attrs)
+        except (KeyError, AttributeError) as exc:
+            if self.default is not empty:
+                return self.get_default()
+            if self.allow_null:
+                return None
+            if not self.required:
+                raise SkipField()
+            msg = (
+                'Got {exc_type} when attempting to get a value for field '
+                '`{field}` on serializer `{serializer}`.\nThe serializer '
+                'field might be named incorrectly and not match '
+                'any attribute or key on the `{instance}` instance.\n'
+                'Original exception text was: {exc}.'.format(
+                    exc_type=type(exc).__name__,
+                    field=self.field_name,
+                    serializer=self.parent.__class__.__name__,
+                    instance=instance.__class__.__name__,
+                    exc=exc
+                )
+            )
+            raise type(exc)(msg)
+
+    def get_default(self):
+        """
+        Return the default value to use when validating data if no input
+        is provided for this field.
+
+        If a default has not been set for this field then this will simply
+        raise `SkipField`, indicating that no value should be set in the
+        validated data for this field.
+        """
+        if self.default is empty or getattr(self.root, 'partial', False):
+            # No default, or this is a partial update.
+            raise SkipField()
+        if callable(self.default):
+            if hasattr(self.default, 'set_context'):
+                self.default.set_context(self)
+            return self.default()
+        return self.default
+
+    def validate_empty_values(self, data):
+        """
+        Validate empty values, and either:
+
+        * Raise `ValidationError`, indicating invalid data.
+        * Raise `SkipField`, indicating that the field should be ignored.
+        * Return (True, data), indicating an empty value that should be
+          returned without any further validation being applied.
+        * Return (False, data), indicating a non-empty value, that should
+          have validation applied as normal.
+        """
+        if self.read_only:
+            return (True, self.get_default())
+
+        if data is empty:
+            if getattr(self.root, 'partial', False):
+                raise SkipField()
+            if self.required:
+                self.fail('required')
+            return (True, self.get_default())
+
+        if data is None:
+            if not self.allow_null:
+                self.fail('null')
+            return (True, None)
+
+        return (False, data)
+
+    def run_validation(self, data=empty):
+        """
+        Validate a simple representation and return the internal value.
+
+        The provided data may be `empty` if no representation was included
+        in the input.
+
+        May raise `SkipField` if the field should not be included in the
+        validated data.
+        """
+        (is_empty_value, data) = self.validate_empty_values(data)
+        if is_empty_value:
+            return data
+        value = self.to_internal_value(data)
+        self.run_validators(value)
+        return value
+
+    def run_validators(self, value):
+        """
+        Test the given value against all the validators on the field,
+        and either raise a `ValidationError` or simply return.
+        """
+        errors = []
+        for validator in self.validators:
+            if hasattr(validator, 'set_context'):
+                validator.set_context(self)
+
+            try:
+                validator(value)
+            except ValidationError as exc:
+                # If the validation error contains a mapping of fields to
+                # errors then simply raise it immediately rather than
+                # attempting to accumulate a list of errors.
+                if isinstance(exc.detail, dict):
+                    raise
+                errors.extend(exc.detail)
+            except DjangoValidationError as exc:
+                errors.extend(get_error_detail(exc))
+        if errors:
+            raise ValidationError(errors)
+
+    def to_internal_value(self, data):
+        """
+        Transform the *incoming* primitive data into a native value.
+        """
+        raise NotImplementedError(
+            '{cls}.to_internal_value() must be implemented.'.format(
+                cls=self.__class__.__name__
+            )
+        )
+
+    def to_representation(self, value):
+        """
+        Transform the *outgoing* native value into primitive data.
+        """
+        raise NotImplementedError(
+            '{cls}.to_representation() must be implemented for field '
+            '{field_name}. If you do not need to support write operations '
+            'you probably want to subclass `ReadOnlyField` instead.'.format(
+                cls=self.__class__.__name__,
+                field_name=self.field_name,
+            )
+        )
+
+    def fail(self, key, **kwargs):
+        """
+        A helper method that simply raises a validation error.
+        """
+        try:
+            msg = self.error_messages[key]
+        except KeyError:
+            class_name = self.__class__.__name__
+            msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
+            raise AssertionError(msg)
+        message_string = msg.format(**kwargs)
+        raise ValidationError(message_string, code=key)
+
+    @property
+    def root(self):
+        """
+        Returns the top-level serializer for this field.
+        """
+        root = self
+        while root.parent is not None:
+            root = root.parent
+        return root
+
+    @property
+    def context(self):
+        """
+        Returns the context as passed to the root serializer on initialization.
+        """
+        return getattr(self.root, '_context', {})
+
+    def __new__(cls, *args, **kwargs):
+        """
+        When a field is instantiated, we store the arguments that were used,
+        so that we can present a helpful representation of the object.
+        """
+        instance = super(Field, cls).__new__(cls)
+        instance._args = args
+        instance._kwargs = kwargs
+        return instance
+
+    def __deepcopy__(self, memo):
+        """
+        When cloning fields we instantiate using the arguments it was
+        originally created with, rather than copying the complete state.
+        """
+        # Treat regexes and validators as immutable.
+        # See https://github.com/encode/django-rest-framework/issues/1954
+        # and https://github.com/encode/django-rest-framework/pull/4489
+        args = [
+            copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item
+            for item in self._args
+        ]
+        kwargs = {
+            key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value)
+            for key, value in self._kwargs.items()
+        }
+        return self.__class__(*args, **kwargs)
+
+    def __repr__(self):
+        """
+        Fields are represented using their initial calling arguments.
+        This allows us to create descriptive representations for serializer
+        instances that show all the declared fields on the serializer.
+        """
+        return unicode_to_repr(representation.field_repr(self))
+
+
+# Boolean types...
+
+class BooleanField(Field):
+    default_error_messages = {
+        'invalid': _('Must be a valid boolean.')
+    }
+    default_empty_html = False
+    initial = False
+    TRUE_VALUES = {
+        't', 'T',
+        'y', 'Y', 'yes', 'YES',
+        'true', 'True', 'TRUE',
+        'on', 'On', 'ON',
+        '1', 1,
+        True
+    }
+    FALSE_VALUES = {
+        'f', 'F',
+        'n', 'N', 'no', 'NO',
+        'false', 'False', 'FALSE',
+        'off', 'Off', 'OFF',
+        '0', 0, 0.0,
+        False
+    }
+    NULL_VALUES = {'null', 'Null', 'NULL', '', None}
+
+    def to_internal_value(self, data):
+        try:
+            if data in self.TRUE_VALUES:
+                return True
+            elif data in self.FALSE_VALUES:
+                return False
+            elif data in self.NULL_VALUES and self.allow_null:
+                return None
+        except TypeError:  # Input is an unhashable type
+            pass
+        self.fail('invalid', input=data)
+
+    def to_representation(self, value):
+        if value in self.TRUE_VALUES:
+            return True
+        elif value in self.FALSE_VALUES:
+            return False
+        if value in self.NULL_VALUES and self.allow_null:
+            return None
+        return bool(value)
+
+
+class NullBooleanField(Field):
+    default_error_messages = {
+        'invalid': _('Must be a valid boolean.')
+    }
+    initial = None
+    TRUE_VALUES = {
+        't', 'T',
+        'y', 'Y', 'yes', 'YES',
+        'true', 'True', 'TRUE',
+        'on', 'On', 'ON',
+        '1', 1,
+        True
+    }
+    FALSE_VALUES = {
+        'f', 'F',
+        'n', 'N', 'no', 'NO',
+        'false', 'False', 'FALSE',
+        'off', 'Off', 'OFF',
+        '0', 0, 0.0,
+        False
+    }
+    NULL_VALUES = {'null', 'Null', 'NULL', '', None}
+
+    def __init__(self, **kwargs):
+        assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
+        kwargs['allow_null'] = True
+        super(NullBooleanField, self).__init__(**kwargs)
+
+    def to_internal_value(self, data):
+        try:
+            if data in self.TRUE_VALUES:
+                return True
+            elif data in self.FALSE_VALUES:
+                return False
+            elif data in self.NULL_VALUES:
+                return None
+        except TypeError:  # Input is an unhashable type
+            pass
+        self.fail('invalid', input=data)
+
+    def to_representation(self, value):
+        if value in self.NULL_VALUES:
+            return None
+        if value in self.TRUE_VALUES:
+            return True
+        elif value in self.FALSE_VALUES:
+            return False
+        return bool(value)
+
+
+# String types...
+
+class CharField(Field):
+    default_error_messages = {
+        'invalid': _('Not a valid string.'),
+        'blank': _('This field may not be blank.'),
+        'max_length': _('Ensure this field has no more than {max_length} characters.'),
+        'min_length': _('Ensure this field has at least {min_length} characters.'),
+    }
+    initial = ''
+
+    def __init__(self, **kwargs):
+        self.allow_blank = kwargs.pop('allow_blank', False)
+        self.trim_whitespace = kwargs.pop('trim_whitespace', True)
+        self.max_length = kwargs.pop('max_length', None)
+        self.min_length = kwargs.pop('min_length', None)
+        super(CharField, self).__init__(**kwargs)
+        if self.max_length is not None:
+            message = lazy(
+                self.error_messages['max_length'].format,
+                six.text_type)(max_length=self.max_length)
+            self.validators.append(
+                MaxLengthValidator(self.max_length, message=message))
+        if self.min_length is not None:
+            message = lazy(
+                self.error_messages['min_length'].format,
+                six.text_type)(min_length=self.min_length)
+            self.validators.append(
+                MinLengthValidator(self.min_length, message=message))
+
+        # ProhibitNullCharactersValidator is None on Django < 2.0
+        if ProhibitNullCharactersValidator is not None:
+            self.validators.append(ProhibitNullCharactersValidator())
+
+    def run_validation(self, data=empty):
+        # Test for the empty string here so that it does not get validated,
+        # and so that subclasses do not need to handle it explicitly
+        # inside the `to_internal_value()` method.
+        if data == '' or (self.trim_whitespace and six.text_type(data).strip() == ''):
+            if not self.allow_blank:
+                self.fail('blank')
+            return ''
+        return super(CharField, self).run_validation(data)
+
+    def to_internal_value(self, data):
+        # We're lenient with allowing basic numerics to be coerced into strings,
+        # but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
+        # and composites such as lists are likely user error.
+        if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)):
+            self.fail('invalid')
+        value = six.text_type(data)
+        return value.strip() if self.trim_whitespace else value
+
+    def to_representation(self, value):
+        return six.text_type(value)
+
+
+class EmailField(CharField):
+    default_error_messages = {
+        'invalid': _('Enter a valid email address.')
+    }
+
+    def __init__(self, **kwargs):
+        super(EmailField, self).__init__(**kwargs)
+        validator = EmailValidator(message=self.error_messages['invalid'])
+        self.validators.append(validator)
+
+
+class RegexField(CharField):
+    default_error_messages = {
+        'invalid': _('This value does not match the required pattern.')
+    }
+
+    def __init__(self, regex, **kwargs):
+        super(RegexField, self).__init__(**kwargs)
+        validator = RegexValidator(regex, message=self.error_messages['invalid'])
+        self.validators.append(validator)
+
+
+class SlugField(CharField):
+    default_error_messages = {
+        'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.'),
+        'invalid_unicode': _('Enter a valid "slug" consisting of Unicode letters, numbers, underscores, or hyphens.')
+    }
+
+    def __init__(self, allow_unicode=False, **kwargs):
+        super(SlugField, self).__init__(**kwargs)
+        self.allow_unicode = allow_unicode
+        if self.allow_unicode:
+            validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode'])
+        else:
+            validator = RegexValidator(re.compile(r'^[-a-zA-Z0-9_]+$'), message=self.error_messages['invalid'])
+        self.validators.append(validator)
+
+
+class URLField(CharField):
+    default_error_messages = {
+        'invalid': _('Enter a valid URL.')
+    }
+
+    def __init__(self, **kwargs):
+        super(URLField, self).__init__(**kwargs)
+        validator = URLValidator(message=self.error_messages['invalid'])
+        self.validators.append(validator)
+
+
+class UUIDField(Field):
+    valid_formats = ('hex_verbose', 'hex', 'int', 'urn')
+
+    default_error_messages = {
+        'invalid': _('Must be a valid UUID.'),
+    }
+
+    def __init__(self, **kwargs):
+        self.uuid_format = kwargs.pop('format', 'hex_verbose')
+        if self.uuid_format not in self.valid_formats:
+            raise ValueError(
+                'Invalid format for uuid representation. '
+                'Must be one of "{0}"'.format('", "'.join(self.valid_formats))
+            )
+        super(UUIDField, self).__init__(**kwargs)
+
+    def to_internal_value(self, data):
+        if not isinstance(data, uuid.UUID):
+            try:
+                if isinstance(data, six.integer_types):
+                    return uuid.UUID(int=data)
+                elif isinstance(data, six.string_types):
+                    return uuid.UUID(hex=data)
+                else:
+                    self.fail('invalid', value=data)
+            except (ValueError):
+                self.fail('invalid', value=data)
+        return data
+
+    def to_representation(self, value):
+        if self.uuid_format == 'hex_verbose':
+            return str(value)
+        else:
+            return getattr(value, self.uuid_format)
+
+
+class IPAddressField(CharField):
+    """Support both IPAddressField and GenericIPAddressField"""
+
+    default_error_messages = {
+        'invalid': _('Enter a valid IPv4 or IPv6 address.'),
+    }
+
+    def __init__(self, protocol='both', **kwargs):
+        self.protocol = protocol.lower()
+        self.unpack_ipv4 = (self.protocol == 'both')
+        super(IPAddressField, self).__init__(**kwargs)
+        validators, error_message = ip_address_validators(protocol, self.unpack_ipv4)
+        self.validators.extend(validators)
+
+    def to_internal_value(self, data):
+        if not isinstance(data, six.string_types):
+            self.fail('invalid', value=data)
+
+        if ':' in data:
+            try:
+                if self.protocol in ('both', 'ipv6'):
+                    return clean_ipv6_address(data, self.unpack_ipv4)
+            except DjangoValidationError:
+                self.fail('invalid', value=data)
+
+        return super(IPAddressField, self).to_internal_value(data)
+
+
+# Number types...
+
+class IntegerField(Field):
+    default_error_messages = {
+        'invalid': _('A valid integer is required.'),
+        'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+        'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+        'max_string_length': _('String value too large.')
+    }
+    MAX_STRING_LENGTH = 1000  # Guard against malicious string inputs.
+    re_decimal = re.compile(r'\.0*\s*$')  # allow e.g. '1.0' as an int, but not '1.2'
+
+    def __init__(self, **kwargs):
+        self.max_value = kwargs.pop('max_value', None)
+        self.min_value = kwargs.pop('min_value', None)
+        super(IntegerField, self).__init__(**kwargs)
+        if self.max_value is not None:
+            message = lazy(
+                self.error_messages['max_value'].format,
+                six.text_type)(max_value=self.max_value)
+            self.validators.append(
+                MaxValueValidator(self.max_value, message=message))
+        if self.min_value is not None:
+            message = lazy(
+                self.error_messages['min_value'].format,
+                six.text_type)(min_value=self.min_value)
+            self.validators.append(
+                MinValueValidator(self.min_value, message=message))
+
+    def to_internal_value(self, data):
+        if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
+            self.fail('max_string_length')
+
+        try:
+            data = int(self.re_decimal.sub('', str(data)))
+        except (ValueError, TypeError):
+            self.fail('invalid')
+        return data
+
+    def to_representation(self, value):
+        return int(value)
+
+
+class FloatField(Field):
+    default_error_messages = {
+        'invalid': _('A valid number is required.'),
+        'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+        'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+        'max_string_length': _('String value too large.')
+    }
+    MAX_STRING_LENGTH = 1000  # Guard against malicious string inputs.
+
+    def __init__(self, **kwargs):
+        self.max_value = kwargs.pop('max_value', None)
+        self.min_value = kwargs.pop('min_value', None)
+        super(FloatField, self).__init__(**kwargs)
+        if self.max_value is not None:
+            message = lazy(
+                self.error_messages['max_value'].format,
+                six.text_type)(max_value=self.max_value)
+            self.validators.append(
+                MaxValueValidator(self.max_value, message=message))
+        if self.min_value is not None:
+            message = lazy(
+                self.error_messages['min_value'].format,
+                six.text_type)(min_value=self.min_value)
+            self.validators.append(
+                MinValueValidator(self.min_value, message=message))
+
+    def to_internal_value(self, data):
+
+        if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
+            self.fail('max_string_length')
+
+        try:
+            return float(data)
+        except (TypeError, ValueError):
+            self.fail('invalid')
+
+    def to_representation(self, value):
+        return float(value)
+
+
+class DecimalField(Field):
+    default_error_messages = {
+        'invalid': _('A valid number is required.'),
+        'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+        'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+        'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
+        'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
+        'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
+        'max_string_length': _('String value too large.')
+    }
+    MAX_STRING_LENGTH = 1000  # Guard against malicious string inputs.
+
+    def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None,
+                 localize=False, rounding=None, **kwargs):
+        self.max_digits = max_digits
+        self.decimal_places = decimal_places
+        self.localize = localize
+        if coerce_to_string is not None:
+            self.coerce_to_string = coerce_to_string
+        if self.localize:
+            self.coerce_to_string = True
+
+        self.max_value = max_value
+        self.min_value = min_value
+
+        if self.max_digits is not None and self.decimal_places is not None:
+            self.max_whole_digits = self.max_digits - self.decimal_places
+        else:
+            self.max_whole_digits = None
+
+        super(DecimalField, self).__init__(**kwargs)
+
+        if self.max_value is not None:
+            message = lazy(
+                self.error_messages['max_value'].format,
+                six.text_type)(max_value=self.max_value)
+            self.validators.append(
+                MaxValueValidator(self.max_value, message=message))
+        if self.min_value is not None:
+            message = lazy(
+                self.error_messages['min_value'].format,
+                six.text_type)(min_value=self.min_value)
+            self.validators.append(
+                MinValueValidator(self.min_value, message=message))
+
+        if rounding is not None:
+            valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')]
+            assert rounding in valid_roundings, (
+                'Invalid rounding option %s. Valid values for rounding are: %s' % (rounding, valid_roundings))
+        self.rounding = rounding
+
+    def to_internal_value(self, data):
+        """
+        Validate that the input is a decimal number and return a Decimal
+        instance.
+        """
+
+        data = smart_text(data).strip()
+
+        if self.localize:
+            data = sanitize_separators(data)
+
+        if len(data) > self.MAX_STRING_LENGTH:
+            self.fail('max_string_length')
+
+        try:
+            value = decimal.Decimal(data)
+        except decimal.DecimalException:
+            self.fail('invalid')
+
+        # Check for NaN. It is the only value that isn't equal to itself,
+        # so we can use this to identify NaN values.
+        if value != value:
+            self.fail('invalid')
+
+        # Check for infinity and negative infinity.
+        if value in (decimal.Decimal('Inf'), decimal.Decimal('-Inf')):
+            self.fail('invalid')
+
+        return self.quantize(self.validate_precision(value))
+
+    def validate_precision(self, value):
+        """
+        Ensure that there are no more than max_digits in the number, and no
+        more than decimal_places digits after the decimal point.
+
+        Override this method to disable the precision validation for input
+        values or to enhance it in any way you need to.
+        """
+        sign, digittuple, exponent = value.as_tuple()
+
+        if exponent >= 0:
+            # 1234500.0
+            total_digits = len(digittuple) + exponent
+            whole_digits = total_digits
+            decimal_places = 0
+        elif len(digittuple) > abs(exponent):
+            # 123.45
+            total_digits = len(digittuple)
+            whole_digits = total_digits - abs(exponent)
+            decimal_places = abs(exponent)
+        else:
+            # 0.001234
+            total_digits = abs(exponent)
+            whole_digits = 0
+            decimal_places = total_digits
+
+        if self.max_digits is not None and total_digits > self.max_digits:
+            self.fail('max_digits', max_digits=self.max_digits)
+        if self.decimal_places is not None and decimal_places > self.decimal_places:
+            self.fail('max_decimal_places', max_decimal_places=self.decimal_places)
+        if self.max_whole_digits is not None and whole_digits > self.max_whole_digits:
+            self.fail('max_whole_digits', max_whole_digits=self.max_whole_digits)
+
+        return value
+
+    def to_representation(self, value):
+        coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING)
+
+        if not isinstance(value, decimal.Decimal):
+            value = decimal.Decimal(six.text_type(value).strip())
+
+        quantized = self.quantize(value)
+
+        if not coerce_to_string:
+            return quantized
+        if self.localize:
+            return localize_input(quantized)
+
+        return '{0:f}'.format(quantized)
+
+    def quantize(self, value):
+        """
+        Quantize the decimal value to the configured precision.
+        """
+        if self.decimal_places is None:
+            return value
+
+        context = decimal.getcontext().copy()
+        if self.max_digits is not None:
+            context.prec = self.max_digits
+        return value.quantize(
+            decimal.Decimal('.1') ** self.decimal_places,
+            rounding=self.rounding,
+            context=context
+        )
+
+
+# Date & time fields...
+
+class DateTimeField(Field):
+    default_error_messages = {
+        'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
+        'date': _('Expected a datetime but got a date.'),
+        'make_aware': _('Invalid datetime for the timezone "{timezone}".'),
+        'overflow': _('Datetime value out of range.')
+    }
+    datetime_parser = datetime.datetime.strptime
+
+    def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs):
+        if format is not empty:
+            self.format = format
+        if input_formats is not None:
+            self.input_formats = input_formats
+        if default_timezone is not None:
+            self.timezone = default_timezone
+        super(DateTimeField, self).__init__(*args, **kwargs)
+
+    def enforce_timezone(self, value):
+        """
+        When `self.default_timezone` is `None`, always return naive datetimes.
+        When `self.default_timezone` is not `None`, always return aware datetimes.
+        """
+        field_timezone = getattr(self, 'timezone', self.default_timezone())
+
+        if field_timezone is not None:
+            if timezone.is_aware(value):
+                try:
+                    return value.astimezone(field_timezone)
+                except OverflowError:
+                    self.fail('overflow')
+            try:
+                return timezone.make_aware(value, field_timezone)
+            except InvalidTimeError:
+                self.fail('make_aware', timezone=field_timezone)
+        elif (field_timezone is None) and timezone.is_aware(value):
+            return timezone.make_naive(value, utc)
+        return value
+
+    def default_timezone(self):
+        return timezone.get_current_timezone() if settings.USE_TZ else None
+
+    def to_internal_value(self, value):
+        input_formats = getattr(self, 'input_formats', api_settings.DATETIME_INPUT_FORMATS)
+
+        if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime):
+            self.fail('date')
+
+        if isinstance(value, datetime.datetime):
+            return self.enforce_timezone(value)
+
+        for input_format in input_formats:
+            if input_format.lower() == ISO_8601:
+                try:
+                    parsed = parse_datetime(value)
+                    if parsed is not None:
+                        return self.enforce_timezone(parsed)
+                except (ValueError, TypeError):
+                    pass
+            else:
+                try:
+                    parsed = self.datetime_parser(value, input_format)
+                    return self.enforce_timezone(parsed)
+                except (ValueError, TypeError):
+                    pass
+
+        humanized_format = humanize_datetime.datetime_formats(input_formats)
+        self.fail('invalid', format=humanized_format)
+
+    def to_representation(self, value):
+        if not value:
+            return None
+
+        output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT)
+
+        if output_format is None or isinstance(value, six.string_types):
+            return value
+
+        value = self.enforce_timezone(value)
+
+        if output_format.lower() == ISO_8601:
+            value = value.isoformat()
+            if value.endswith('+00:00'):
+                value = value[:-6] + 'Z'
+            return value
+        return value.strftime(output_format)
+
+
+class DateField(Field):
+    default_error_messages = {
+        'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
+        'datetime': _('Expected a date but got a datetime.'),
+    }
+    datetime_parser = datetime.datetime.strptime
+
+    def __init__(self, format=empty, input_formats=None, *args, **kwargs):
+        if format is not empty:
+            self.format = format
+        if input_formats is not None:
+            self.input_formats = input_formats
+        super(DateField, self).__init__(*args, **kwargs)
+
+    def to_internal_value(self, value):
+        input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
+
+        if isinstance(value, datetime.datetime):
+            self.fail('datetime')
+
+        if isinstance(value, datetime.date):
+            return value
+
+        for input_format in input_formats:
+            if input_format.lower() == ISO_8601:
+                try:
+                    parsed = parse_date(value)
+                except (ValueError, TypeError):
+                    pass
+                else:
+                    if parsed is not None:
+                        return parsed
+            else:
+                try:
+                    parsed = self.datetime_parser(value, input_format)
+                except (ValueError, TypeError):
+                    pass
+                else:
+                    return parsed.date()
+
+        humanized_format = humanize_datetime.date_formats(input_formats)
+        self.fail('invalid', format=humanized_format)
+
+    def to_representation(self, value):
+        if not value:
+            return None
+
+        output_format = getattr(self, 'format', api_settings.DATE_FORMAT)
+
+        if output_format is None or isinstance(value, six.string_types):
+            return value
+
+        # Applying a `DateField` to a datetime value is almost always
+        # not a sensible thing to do, as it means naively dropping
+        # any explicit or implicit timezone info.
+        assert not isinstance(value, datetime.datetime), (
+            'Expected a `date`, but got a `datetime`. Refusing to coerce, '
+            'as this may mean losing timezone information. Use a custom '
+            'read-only field and deal with timezone issues explicitly.'
+        )
+
+        if output_format.lower() == ISO_8601:
+            return value.isoformat()
+
+        return value.strftime(output_format)
+
+
+class TimeField(Field):
+    default_error_messages = {
+        'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
+    }
+    datetime_parser = datetime.datetime.strptime
+
+    def __init__(self, format=empty, input_formats=None, *args, **kwargs):
+        if format is not empty:
+            self.format = format
+        if input_formats is not None:
+            self.input_formats = input_formats
+        super(TimeField, self).__init__(*args, **kwargs)
+
+    def to_internal_value(self, value):
+        input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
+
+        if isinstance(value, datetime.time):
+            return value
+
+        for input_format in input_formats:
+            if input_format.lower() == ISO_8601:
+                try:
+                    parsed = parse_time(value)
+                except (ValueError, TypeError):
+                    pass
+                else:
+                    if parsed is not None:
+                        return parsed
+            else:
+                try:
+                    parsed = self.datetime_parser(value, input_format)
+                except (ValueError, TypeError):
+                    pass
+                else:
+                    return parsed.time()
+
+        humanized_format = humanize_datetime.time_formats(input_formats)
+        self.fail('invalid', format=humanized_format)
+
+    def to_representation(self, value):
+        if value in (None, ''):
+            return None
+
+        output_format = getattr(self, 'format', api_settings.TIME_FORMAT)
+
+        if output_format is None or isinstance(value, six.string_types):
+            return value
+
+        # Applying a `TimeField` to a datetime value is almost always
+        # not a sensible thing to do, as it means naively dropping
+        # any explicit or implicit timezone info.
+        assert not isinstance(value, datetime.datetime), (
+            'Expected a `time`, but got a `datetime`. Refusing to coerce, '
+            'as this may mean losing timezone information. Use a custom '
+            'read-only field and deal with timezone issues explicitly.'
+        )
+
+        if output_format.lower() == ISO_8601:
+            return value.isoformat()
+        return value.strftime(output_format)
+
+
+class DurationField(Field):
+    default_error_messages = {
+        'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
+        'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+        'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+    }
+
+    def __init__(self, **kwargs):
+        self.max_value = kwargs.pop('max_value', None)
+        self.min_value = kwargs.pop('min_value', None)
+        super(DurationField, self).__init__(**kwargs)
+        if self.max_value is not None:
+            message = lazy(
+                self.error_messages['max_value'].format,
+                six.text_type)(max_value=self.max_value)
+            self.validators.append(
+                MaxValueValidator(self.max_value, message=message))
+        if self.min_value is not None:
+            message = lazy(
+                self.error_messages['min_value'].format,
+                six.text_type)(min_value=self.min_value)
+            self.validators.append(
+                MinValueValidator(self.min_value, message=message))
+
+    def to_internal_value(self, value):
+        if isinstance(value, datetime.timedelta):
+            return value
+        parsed = parse_duration(six.text_type(value))
+        if parsed is not None:
+            return parsed
+        self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
+
+    def to_representation(self, value):
+        return duration_string(value)
+
+
+# Choice types...
+
+class ChoiceField(Field):
+    default_error_messages = {
+        'invalid_choice': _('"{input}" is not a valid choice.')
+    }
+    html_cutoff = None
+    html_cutoff_text = _('More than {count} items...')
+
+    def __init__(self, choices, **kwargs):
+        self.choices = choices
+        self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
+        self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
+
+        self.allow_blank = kwargs.pop('allow_blank', False)
+
+        super(ChoiceField, self).__init__(**kwargs)
+
+    def to_internal_value(self, data):
+        if data == '' and self.allow_blank:
+            return ''
+
+        try:
+            return self.choice_strings_to_values[six.text_type(data)]
+        except KeyError:
+            self.fail('invalid_choice', input=data)
+
+    def to_representation(self, value):
+        if value in ('', None):
+            return value
+        return self.choice_strings_to_values.get(six.text_type(value), value)
+
+    def iter_options(self):
+        """
+        Helper method for use with templates rendering select widgets.
+        """
+        return iter_options(
+            self.grouped_choices,
+            cutoff=self.html_cutoff,
+            cutoff_text=self.html_cutoff_text
+        )
+
+    def _get_choices(self):
+        return self._choices
+
+    def _set_choices(self, choices):
+        self.grouped_choices = to_choices_dict(choices)
+        self._choices = flatten_choices_dict(self.grouped_choices)
+
+        # Map the string representation of choices to the underlying value.
+        # Allows us to deal with eg. integer choices while supporting either
+        # integer or string input, but still get the correct datatype out.
+        self.choice_strings_to_values = {
+            six.text_type(key): key for key in self.choices
+        }
+
+    choices = property(_get_choices, _set_choices)
+
+
+class MultipleChoiceField(ChoiceField):
+    default_error_messages = {
+        'invalid_choice': _('"{input}" is not a valid choice.'),
+        'not_a_list': _('Expected a list of items but got type "{input_type}".'),
+        'empty': _('This selection may not be empty.')
+    }
+    default_empty_html = []
+
+    def __init__(self, *args, **kwargs):
+        self.allow_empty = kwargs.pop('allow_empty', True)
+        super(MultipleChoiceField, self).__init__(*args, **kwargs)
+
+    def get_value(self, dictionary):
+        if self.field_name not in dictionary:
+            if getattr(self.root, 'partial', False):
+                return empty
+        # We override the default field access in order to support
+        # lists in HTML forms.
+        if html.is_html_input(dictionary):
+            return dictionary.getlist(self.field_name)
+        return dictionary.get(self.field_name, empty)
+
+    def to_internal_value(self, data):
+        if isinstance(data, six.text_type) or not hasattr(data, '__iter__'):
+            self.fail('not_a_list', input_type=type(data).__name__)
+        if not self.allow_empty and len(data) == 0:
+            self.fail('empty')
+
+        return {
+            super(MultipleChoiceField, self).to_internal_value(item)
+            for item in data
+        }
+
+    def to_representation(self, value):
+        return {
+            self.choice_strings_to_values.get(six.text_type(item), item) for item in value
+        }
+
+
+class FilePathField(ChoiceField):
+    default_error_messages = {
+        'invalid_choice': _('"{input}" is not a valid path choice.')
+    }
+
+    def __init__(self, path, match=None, recursive=False, allow_files=True,
+                 allow_folders=False, required=None, **kwargs):
+        # Defer to Django's FilePathField implementation to get the
+        # valid set of choices.
+        field = DjangoFilePathField(
+            path, match=match, recursive=recursive, allow_files=allow_files,
+            allow_folders=allow_folders, required=required
+        )
+        kwargs['choices'] = field.choices
+        super(FilePathField, self).__init__(**kwargs)
+
+
+# File types...
+
+class FileField(Field):
+    default_error_messages = {
+        'required': _('No file was submitted.'),
+        'invalid': _('The submitted data was not a file. Check the encoding type on the form.'),
+        'no_name': _('No filename could be determined.'),
+        'empty': _('The submitted file is empty.'),
+        'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.max_length = kwargs.pop('max_length', None)
+        self.allow_empty_file = kwargs.pop('allow_empty_file', False)
+        if 'use_url' in kwargs:
+            self.use_url = kwargs.pop('use_url')
+        super(FileField, self).__init__(*args, **kwargs)
+
+    def to_internal_value(self, data):
+        try:
+            # `UploadedFile` objects should have name and size attributes.
+            file_name = data.name
+            file_size = data.size
+        except AttributeError:
+            self.fail('invalid')
+
+        if not file_name:
+            self.fail('no_name')
+        if not self.allow_empty_file and not file_size:
+            self.fail('empty')
+        if self.max_length and len(file_name) > self.max_length:
+            self.fail('max_length', max_length=self.max_length, length=len(file_name))
+
+        return data
+
+    def to_representation(self, value):
+        if not value:
+            return None
+
+        use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
+
+        if use_url:
+            if not getattr(value, 'url', None):
+                # If the file has not been saved it may not have a URL.
+                return None
+            url = value.url
+            request = self.context.get('request', None)
+            if request is not None:
+                return request.build_absolute_uri(url)
+            return url
+        return value.name
+
+
+class ImageField(FileField):
+    default_error_messages = {
+        'invalid_image': _(
+            'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
+        ),
+    }
+
+    def __init__(self, *args, **kwargs):
+        self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
+        super(ImageField, self).__init__(*args, **kwargs)
+
+    def to_internal_value(self, data):
+        # Image validation is a bit grungy, so we'll just outright
+        # defer to Django's implementation so we don't need to
+        # consider it, or treat PIL as a test dependency.
+        file_object = super(ImageField, self).to_internal_value(data)
+        django_field = self._DjangoImageField()
+        django_field.error_messages = self.error_messages
+        return django_field.clean(file_object)
+
+
+# Composite field types...
+
+class _UnvalidatedField(Field):
+    def __init__(self, *args, **kwargs):
+        super(_UnvalidatedField, self).__init__(*args, **kwargs)
+        self.allow_blank = True
+        self.allow_null = True
+
+    def to_internal_value(self, data):
+        return data
+
+    def to_representation(self, value):
+        return value
+
+
+class ListField(Field):
+    child = _UnvalidatedField()
+    initial = []
+    default_error_messages = {
+        'not_a_list': _('Expected a list of items but got type "{input_type}".'),
+        'empty': _('This list may not be empty.'),
+        'min_length': _('Ensure this field has at least {min_length} elements.'),
+        'max_length': _('Ensure this field has no more than {max_length} elements.')
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.child = kwargs.pop('child', copy.deepcopy(self.child))
+        self.allow_empty = kwargs.pop('allow_empty', True)
+        self.max_length = kwargs.pop('max_length', None)
+        self.min_length = kwargs.pop('min_length', None)
+
+        assert not inspect.isclass(self.child), '`child` has not been instantiated.'
+        assert self.child.source is None, (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
+        super(ListField, self).__init__(*args, **kwargs)
+        self.child.bind(field_name='', parent=self)
+        if self.max_length is not None:
+            message = self.error_messages['max_length'].format(max_length=self.max_length)
+            self.validators.append(MaxLengthValidator(self.max_length, message=message))
+        if self.min_length is not None:
+            message = self.error_messages['min_length'].format(min_length=self.min_length)
+            self.validators.append(MinLengthValidator(self.min_length, message=message))
+
+    def get_value(self, dictionary):
+        if self.field_name not in dictionary:
+            if getattr(self.root, 'partial', False):
+                return empty
+        # We override the default field access in order to support
+        # lists in HTML forms.
+        if html.is_html_input(dictionary):
+            val = dictionary.getlist(self.field_name, [])
+            if len(val) > 0:
+                # Support QueryDict lists in HTML input.
+                return val
+            return html.parse_html_list(dictionary, prefix=self.field_name, default=empty)
+
+        return dictionary.get(self.field_name, empty)
+
+    def to_internal_value(self, data):
+        """
+        List of dicts of native values <- List of dicts of primitive datatypes.
+        """
+        if html.is_html_input(data):
+            data = html.parse_html_list(data, default=[])
+        if isinstance(data, (six.text_type, Mapping)) or not hasattr(data, '__iter__'):
+            self.fail('not_a_list', input_type=type(data).__name__)
+        if not self.allow_empty and len(data) == 0:
+            self.fail('empty')
+        return self.run_child_validation(data)
+
+    def to_representation(self, data):
+        """
+        List of object instances -> List of dicts of primitive datatypes.
+        """
+        return [self.child.to_representation(item) if item is not None else None for item in data]
+
+    def run_child_validation(self, data):
+        result = []
+        errors = OrderedDict()
+
+        for idx, item in enumerate(data):
+            try:
+                result.append(self.child.run_validation(item))
+            except ValidationError as e:
+                errors[idx] = e.detail
+
+        if not errors:
+            return result
+        raise ValidationError(errors)
+
+
+class DictField(Field):
+    child = _UnvalidatedField()
+    initial = {}
+    default_error_messages = {
+        'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".')
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.child = kwargs.pop('child', copy.deepcopy(self.child))
+
+        assert not inspect.isclass(self.child), '`child` has not been instantiated.'
+        assert self.child.source is None, (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
+        super(DictField, self).__init__(*args, **kwargs)
+        self.child.bind(field_name='', parent=self)
+
+    def get_value(self, dictionary):
+        # We override the default field access in order to support
+        # dictionaries in HTML forms.
+        if html.is_html_input(dictionary):
+            return html.parse_html_dict(dictionary, prefix=self.field_name)
+        return dictionary.get(self.field_name, empty)
+
+    def to_internal_value(self, data):
+        """
+        Dicts of native values <- Dicts of primitive datatypes.
+        """
+        if html.is_html_input(data):
+            data = html.parse_html_dict(data)
+        if not isinstance(data, dict):
+            self.fail('not_a_dict', input_type=type(data).__name__)
+        return self.run_child_validation(data)
+
+    def to_representation(self, value):
+        return {
+            six.text_type(key): self.child.to_representation(val) if val is not None else None
+            for key, val in value.items()
+        }
+
+    def run_child_validation(self, data):
+        result = {}
+        errors = OrderedDict()
+
+        for key, value in data.items():
+            key = six.text_type(key)
+
+            try:
+                result[key] = self.child.run_validation(value)
+            except ValidationError as e:
+                errors[key] = e.detail
+
+        if not errors:
+            return result
+        raise ValidationError(errors)
+
+
+class HStoreField(DictField):
+    child = CharField(allow_blank=True, allow_null=True)
+
+    def __init__(self, *args, **kwargs):
+        super(HStoreField, self).__init__(*args, **kwargs)
+        assert isinstance(self.child, CharField), (
+            "The `child` argument must be an instance of `CharField`, "
+            "as the hstore extension stores values as strings."
+        )
+
+
+class JSONField(Field):
+    default_error_messages = {
+        'invalid': _('Value must be valid JSON.')
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.binary = kwargs.pop('binary', False)
+        super(JSONField, self).__init__(*args, **kwargs)
+
+    def get_value(self, dictionary):
+        if html.is_html_input(dictionary) and self.field_name in dictionary:
+            # When HTML form input is used, mark up the input
+            # as being a JSON string, rather than a JSON primitive.
+            class JSONString(six.text_type):
+                def __new__(self, value):
+                    ret = six.text_type.__new__(self, value)
+                    ret.is_json_string = True
+                    return ret
+            return JSONString(dictionary[self.field_name])
+        return dictionary.get(self.field_name, empty)
+
+    def to_internal_value(self, data):
+        try:
+            if self.binary or getattr(data, 'is_json_string', False):
+                if isinstance(data, bytes):
+                    data = data.decode('utf-8')
+                return json.loads(data)
+            else:
+                json.dumps(data)
+        except (TypeError, ValueError):
+            self.fail('invalid')
+        return data
+
+    def to_representation(self, value):
+        if self.binary:
+            value = json.dumps(value)
+            # On python 2.x the return type for json.dumps() is underspecified.
+            # On python 3.x json.dumps() returns unicode strings.
+            if isinstance(value, six.text_type):
+                value = bytes(value.encode('utf-8'))
+        return value
+
+
+# Miscellaneous field types...
+
+class ReadOnlyField(Field):
+    """
+    A read-only field that simply returns the field value.
+
+    If the field is a method with no parameters, the method will be called
+    and its return value used as the representation.
+
+    For example, the following would call `get_expiry_date()` on the object:
+
+    class ExampleSerializer(Serializer):
+        expiry_date = ReadOnlyField(source='get_expiry_date')
+    """
+
+    def __init__(self, **kwargs):
+        kwargs['read_only'] = True
+        super(ReadOnlyField, self).__init__(**kwargs)
+
+    def to_representation(self, value):
+        return value
+
+
+class HiddenField(Field):
+    """
+    A hidden field does not take input from the user, or present any output,
+    but it does populate a field in `validated_data`, based on its default
+    value. This is particularly useful when we have a `unique_for_date`
+    constraint on a pair of fields, as we need some way to include the date in
+    the validated data.
+    """
+    def __init__(self, **kwargs):
+        assert 'default' in kwargs, 'default is a required argument.'
+        kwargs['write_only'] = True
+        super(HiddenField, self).__init__(**kwargs)
+
+    def get_value(self, dictionary):
+        # We always use the default value for `HiddenField`.
+        # User input is never provided or accepted.
+        return empty
+
+    def to_internal_value(self, data):
+        return data
+
+
+class SerializerMethodField(Field):
+    """
+    A read-only field that get its representation from calling a method on the
+    parent serializer class. The method called will be of the form
+    "get_{field_name}", and should take a single argument, which is the
+    object being serialized.
+
+    For example:
+
+    class ExampleSerializer(self):
+        extra_info = SerializerMethodField()
+
+        def get_extra_info(self, obj):
+            return ...  # Calculate some data to return.
+    """
+    def __init__(self, method_name=None, **kwargs):
+        self.method_name = method_name
+        kwargs['source'] = '*'
+        kwargs['read_only'] = True
+        super(SerializerMethodField, self).__init__(**kwargs)
+
+    def bind(self, field_name, parent):
+        # In order to enforce a consistent style, we error if a redundant
+        # 'method_name' argument has been used. For example:
+        # my_field = serializer.SerializerMethodField(method_name='get_my_field')
+        default_method_name = 'get_{field_name}'.format(field_name=field_name)
+        assert self.method_name != default_method_name, (
+            "It is redundant to specify `%s` on SerializerMethodField '%s' in "
+            "serializer '%s', because it is the same as the default method name. "
+            "Remove the `method_name` argument." %
+            (self.method_name, field_name, parent.__class__.__name__)
+        )
+
+        # The method name should default to `get_{field_name}`.
+        if self.method_name is None:
+            self.method_name = default_method_name
+
+        super(SerializerMethodField, self).bind(field_name, parent)
+
+    def to_representation(self, value):
+        method = getattr(self.parent, self.method_name)
+        return method(value)
+
+
+class ModelField(Field):
+    """
+    A generic field that can be used against an arbitrary model field.
+
+    This is used by `ModelSerializer` when dealing with custom model fields,
+    that do not have a serializer field to be mapped to.
+    """
+    default_error_messages = {
+        'max_length': _('Ensure this field has no more than {max_length} characters.'),
+    }
+
+    def __init__(self, model_field, **kwargs):
+        self.model_field = model_field
+        # The `max_length` option is supported by Django's base `Field` class,
+        # so we'd better support it here.
+        max_length = kwargs.pop('max_length', None)
+        super(ModelField, self).__init__(**kwargs)
+        if max_length is not None:
+            message = lazy(
+                self.error_messages['max_length'].format,
+                six.text_type)(max_length=self.max_length)
+            self.validators.append(
+                MaxLengthValidator(self.max_length, message=message))
+
+    def to_internal_value(self, data):
+        rel = self.model_field.remote_field
+        if rel is not None:
+            return rel.model._meta.get_field(rel.field_name).to_python(data)
+        return self.model_field.to_python(data)
+
+    def get_attribute(self, obj):
+        # We pass the object instance onto `to_representation`,
+        # not just the field attribute.
+        return obj
+
+    def to_representation(self, obj):
+        value = self.model_field.value_from_object(obj)
+        if is_protected_type(value):
+            return value
+        return self.model_field.value_to_string(obj)

+ 329 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/filters.py

@@ -0,0 +1,329 @@
+"""
+Provides generic filtering backends that can be used to filter the results
+returned by list views.
+"""
+from __future__ import unicode_literals
+
+import operator
+import warnings
+from functools import reduce
+
+from django.core.exceptions import ImproperlyConfigured
+from django.db import models
+from django.db.models.constants import LOOKUP_SEP
+from django.db.models.sql.constants import ORDER_PATTERN
+from django.template import loader
+from django.utils import six
+from django.utils.encoding import force_text
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework import RemovedInDRF310Warning
+from rest_framework.compat import (
+    coreapi, coreschema, distinct, is_guardian_installed
+)
+from rest_framework.settings import api_settings
+
+
+class BaseFilterBackend(object):
+    """
+    A base class from which all filter backend classes should inherit.
+    """
+
+    def filter_queryset(self, request, queryset, view):
+        """
+        Return a filtered queryset.
+        """
+        raise NotImplementedError(".filter_queryset() must be overridden.")
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
+        return []
+
+
+class SearchFilter(BaseFilterBackend):
+    # The URL query parameter used for the search.
+    search_param = api_settings.SEARCH_PARAM
+    template = 'rest_framework/filters/search.html'
+    lookup_prefixes = {
+        '^': 'istartswith',
+        '=': 'iexact',
+        '@': 'search',
+        '$': 'iregex',
+    }
+    search_title = _('Search')
+    search_description = _('A search term.')
+
+    def get_search_fields(self, view, request):
+        """
+        Search fields are obtained from the view, but the request is always
+        passed to this method. Sub-classes can override this method to
+        dynamically change the search fields based on request content.
+        """
+        return getattr(view, 'search_fields', None)
+
+    def get_search_terms(self, request):
+        """
+        Search terms are set by a ?search=... query parameter,
+        and may be comma and/or whitespace delimited.
+        """
+        params = request.query_params.get(self.search_param, '')
+        return params.replace(',', ' ').split()
+
+    def construct_search(self, field_name):
+        lookup = self.lookup_prefixes.get(field_name[0])
+        if lookup:
+            field_name = field_name[1:]
+        else:
+            lookup = 'icontains'
+        return LOOKUP_SEP.join([field_name, lookup])
+
+    def must_call_distinct(self, queryset, search_fields):
+        """
+        Return True if 'distinct()' should be used to query the given lookups.
+        """
+        for search_field in search_fields:
+            opts = queryset.model._meta
+            if search_field[0] in self.lookup_prefixes:
+                search_field = search_field[1:]
+            # Annotated fields do not need to be distinct
+            if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations:
+                return False
+            parts = search_field.split(LOOKUP_SEP)
+            for part in parts:
+                field = opts.get_field(part)
+                if hasattr(field, 'get_path_info'):
+                    # This field is a relation, update opts to follow the relation
+                    path_info = field.get_path_info()
+                    opts = path_info[-1].to_opts
+                    if any(path.m2m for path in path_info):
+                        # This field is a m2m relation so we know we need to call distinct
+                        return True
+        return False
+
+    def filter_queryset(self, request, queryset, view):
+        search_fields = self.get_search_fields(view, request)
+        search_terms = self.get_search_terms(request)
+
+        if not search_fields or not search_terms:
+            return queryset
+
+        orm_lookups = [
+            self.construct_search(six.text_type(search_field))
+            for search_field in search_fields
+        ]
+
+        base = queryset
+        conditions = []
+        for search_term in search_terms:
+            queries = [
+                models.Q(**{orm_lookup: search_term})
+                for orm_lookup in orm_lookups
+            ]
+            conditions.append(reduce(operator.or_, queries))
+        queryset = queryset.filter(reduce(operator.and_, conditions))
+
+        if self.must_call_distinct(queryset, search_fields):
+            # Filtering against a many-to-many field requires us to
+            # call queryset.distinct() in order to avoid duplicate items
+            # in the resulting queryset.
+            # We try to avoid this if possible, for performance reasons.
+            queryset = distinct(queryset, base)
+        return queryset
+
+    def to_html(self, request, queryset, view):
+        if not getattr(view, 'search_fields', None):
+            return ''
+
+        term = self.get_search_terms(request)
+        term = term[0] if term else ''
+        context = {
+            'param': self.search_param,
+            'term': term
+        }
+        template = loader.get_template(self.template)
+        return template.render(context)
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
+        return [
+            coreapi.Field(
+                name=self.search_param,
+                required=False,
+                location='query',
+                schema=coreschema.String(
+                    title=force_text(self.search_title),
+                    description=force_text(self.search_description)
+                )
+            )
+        ]
+
+
+class OrderingFilter(BaseFilterBackend):
+    # The URL query parameter used for the ordering.
+    ordering_param = api_settings.ORDERING_PARAM
+    ordering_fields = None
+    ordering_title = _('Ordering')
+    ordering_description = _('Which field to use when ordering the results.')
+    template = 'rest_framework/filters/ordering.html'
+
+    def get_ordering(self, request, queryset, view):
+        """
+        Ordering is set by a comma delimited ?ordering=... query parameter.
+
+        The `ordering` query parameter can be overridden by setting
+        the `ordering_param` value on the OrderingFilter or by
+        specifying an `ORDERING_PARAM` value in the API settings.
+        """
+        params = request.query_params.get(self.ordering_param)
+        if params:
+            fields = [param.strip() for param in params.split(',')]
+            ordering = self.remove_invalid_fields(queryset, fields, view, request)
+            if ordering:
+                return ordering
+
+        # No ordering was included, or all the ordering fields were invalid
+        return self.get_default_ordering(view)
+
+    def get_default_ordering(self, view):
+        ordering = getattr(view, 'ordering', None)
+        if isinstance(ordering, six.string_types):
+            return (ordering,)
+        return ordering
+
+    def get_default_valid_fields(self, queryset, view, context={}):
+        # If `ordering_fields` is not specified, then we determine a default
+        # based on the serializer class, if one exists on the view.
+        if hasattr(view, 'get_serializer_class'):
+            try:
+                serializer_class = view.get_serializer_class()
+            except AssertionError:
+                # Raised by the default implementation if
+                # no serializer_class was found
+                serializer_class = None
+        else:
+            serializer_class = getattr(view, 'serializer_class', None)
+
+        if serializer_class is None:
+            msg = (
+                "Cannot use %s on a view which does not have either a "
+                "'serializer_class', an overriding 'get_serializer_class' "
+                "or 'ordering_fields' attribute."
+            )
+            raise ImproperlyConfigured(msg % self.__class__.__name__)
+
+        return [
+            (field.source.replace('.', '__') or field_name, field.label)
+            for field_name, field in serializer_class(context=context).fields.items()
+            if not getattr(field, 'write_only', False) and not field.source == '*'
+        ]
+
+    def get_valid_fields(self, queryset, view, context={}):
+        valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
+
+        if valid_fields is None:
+            # Default to allowing filtering on serializer fields
+            return self.get_default_valid_fields(queryset, view, context)
+
+        elif valid_fields == '__all__':
+            # View explicitly allows filtering on any model field
+            valid_fields = [
+                (field.name, field.verbose_name) for field in queryset.model._meta.fields
+            ]
+            valid_fields += [
+                (key, key.title().split('__'))
+                for key in queryset.query.annotations
+            ]
+        else:
+            valid_fields = [
+                (item, item) if isinstance(item, six.string_types) else item
+                for item in valid_fields
+            ]
+
+        return valid_fields
+
+    def remove_invalid_fields(self, queryset, fields, view, request):
+        valid_fields = [item[0] for item in self.get_valid_fields(queryset, view, {'request': request})]
+        return [term for term in fields if term.lstrip('-') in valid_fields and ORDER_PATTERN.match(term)]
+
+    def filter_queryset(self, request, queryset, view):
+        ordering = self.get_ordering(request, queryset, view)
+
+        if ordering:
+            return queryset.order_by(*ordering)
+
+        return queryset
+
+    def get_template_context(self, request, queryset, view):
+        current = self.get_ordering(request, queryset, view)
+        current = None if not current else current[0]
+        options = []
+        context = {
+            'request': request,
+            'current': current,
+            'param': self.ordering_param,
+        }
+        for key, label in self.get_valid_fields(queryset, view, context):
+            options.append((key, '%s - %s' % (label, _('ascending'))))
+            options.append(('-' + key, '%s - %s' % (label, _('descending'))))
+        context['options'] = options
+        return context
+
+    def to_html(self, request, queryset, view):
+        template = loader.get_template(self.template)
+        context = self.get_template_context(request, queryset, view)
+        return template.render(context)
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
+        return [
+            coreapi.Field(
+                name=self.ordering_param,
+                required=False,
+                location='query',
+                schema=coreschema.String(
+                    title=force_text(self.ordering_title),
+                    description=force_text(self.ordering_description)
+                )
+            )
+        ]
+
+
+class DjangoObjectPermissionsFilter(BaseFilterBackend):
+    """
+    A filter backend that limits results to those where the requesting user
+    has read object level permissions.
+    """
+    def __init__(self):
+        warnings.warn(
+            "`DjangoObjectPermissionsFilter` has been deprecated and moved to "
+            "the 3rd-party django-rest-framework-guardian package.",
+            RemovedInDRF310Warning, stacklevel=2
+        )
+        assert is_guardian_installed(), 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed'
+
+    perm_format = '%(app_label)s.view_%(model_name)s'
+
+    def filter_queryset(self, request, queryset, view):
+        # We want to defer this import until run-time, rather than import-time.
+        # See https://github.com/encode/django-rest-framework/issues/4608
+        # (Also see #1624 for why we need to make this import explicitly)
+        from guardian import VERSION as guardian_version
+        from guardian.shortcuts import get_objects_for_user
+
+        extra = {}
+        user = request.user
+        model_cls = queryset.model
+        kwargs = {
+            'app_label': model_cls._meta.app_label,
+            'model_name': model_cls._meta.model_name
+        }
+        permission = self.perm_format % kwargs
+        if tuple(guardian_version) >= (1, 3):
+            # Maintain behavior compatibility with versions prior to 1.3
+            extra = {'accept_global_perms': False}
+        else:
+            extra = {}
+        return get_objects_for_user(user, permission, queryset, **extra)

+ 293 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/generics.py

@@ -0,0 +1,293 @@
+"""
+Generic views that provide commonly needed behaviour.
+"""
+from __future__ import unicode_literals
+
+from django.core.exceptions import ValidationError
+from django.db.models.query import QuerySet
+from django.http import Http404
+from django.shortcuts import get_object_or_404 as _get_object_or_404
+
+from rest_framework import mixins, views
+from rest_framework.settings import api_settings
+
+
+def get_object_or_404(queryset, *filter_args, **filter_kwargs):
+    """
+    Same as Django's standard shortcut, but make sure to also raise 404
+    if the filter_kwargs don't match the required types.
+    """
+    try:
+        return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
+    except (TypeError, ValueError, ValidationError):
+        raise Http404
+
+
+class GenericAPIView(views.APIView):
+    """
+    Base class for all other generic views.
+    """
+    # You'll need to either set these attributes,
+    # or override `get_queryset()`/`get_serializer_class()`.
+    # If you are overriding a view method, it is important that you call
+    # `get_queryset()` instead of accessing the `queryset` property directly,
+    # as `queryset` will get evaluated only once, and those results are cached
+    # for all subsequent requests.
+    queryset = None
+    serializer_class = None
+
+    # If you want to use object lookups other than pk, set 'lookup_field'.
+    # For more complex lookup requirements override `get_object()`.
+    lookup_field = 'pk'
+    lookup_url_kwarg = None
+
+    # The filter backend classes to use for queryset filtering
+    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
+
+    # The style to use for queryset pagination.
+    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
+
+    def get_queryset(self):
+        """
+        Get the list of items for this view.
+        This must be an iterable, and may be a queryset.
+        Defaults to using `self.queryset`.
+
+        This method should always be used rather than accessing `self.queryset`
+        directly, as `self.queryset` gets evaluated only once, and those results
+        are cached for all subsequent requests.
+
+        You may want to override this if you need to provide different
+        querysets depending on the incoming request.
+
+        (Eg. return a list of items that is specific to the user)
+        """
+        assert self.queryset is not None, (
+            "'%s' should either include a `queryset` attribute, "
+            "or override the `get_queryset()` method."
+            % self.__class__.__name__
+        )
+
+        queryset = self.queryset
+        if isinstance(queryset, QuerySet):
+            # Ensure queryset is re-evaluated on each request.
+            queryset = queryset.all()
+        return queryset
+
+    def get_object(self):
+        """
+        Returns the object the view is displaying.
+
+        You may want to override this if you need to provide non-standard
+        queryset lookups.  Eg if objects are referenced using multiple
+        keyword arguments in the url conf.
+        """
+        queryset = self.filter_queryset(self.get_queryset())
+
+        # Perform the lookup filtering.
+        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
+
+        assert lookup_url_kwarg in self.kwargs, (
+            'Expected view %s to be called with a URL keyword argument '
+            'named "%s". Fix your URL conf, or set the `.lookup_field` '
+            'attribute on the view correctly.' %
+            (self.__class__.__name__, lookup_url_kwarg)
+        )
+
+        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
+        obj = get_object_or_404(queryset, **filter_kwargs)
+
+        # May raise a permission denied
+        self.check_object_permissions(self.request, obj)
+
+        return obj
+
+    def get_serializer(self, *args, **kwargs):
+        """
+        Return the serializer instance that should be used for validating and
+        deserializing input, and for serializing output.
+        """
+        serializer_class = self.get_serializer_class()
+        kwargs['context'] = self.get_serializer_context()
+        return serializer_class(*args, **kwargs)
+
+    def get_serializer_class(self):
+        """
+        Return the class to use for the serializer.
+        Defaults to using `self.serializer_class`.
+
+        You may want to override this if you need to provide different
+        serializations depending on the incoming request.
+
+        (Eg. admins get full serialization, others get basic serialization)
+        """
+        assert self.serializer_class is not None, (
+            "'%s' should either include a `serializer_class` attribute, "
+            "or override the `get_serializer_class()` method."
+            % self.__class__.__name__
+        )
+
+        return self.serializer_class
+
+    def get_serializer_context(self):
+        """
+        Extra context provided to the serializer class.
+        """
+        return {
+            'request': self.request,
+            'format': self.format_kwarg,
+            'view': self
+        }
+
+    def filter_queryset(self, queryset):
+        """
+        Given a queryset, filter it with whichever filter backend is in use.
+
+        You are unlikely to want to override this method, although you may need
+        to call it either from a list view, or from a custom `get_object`
+        method if you want to apply the configured filtering backend to the
+        default queryset.
+        """
+        for backend in list(self.filter_backends):
+            queryset = backend().filter_queryset(self.request, queryset, self)
+        return queryset
+
+    @property
+    def paginator(self):
+        """
+        The paginator instance associated with the view, or `None`.
+        """
+        if not hasattr(self, '_paginator'):
+            if self.pagination_class is None:
+                self._paginator = None
+            else:
+                self._paginator = self.pagination_class()
+        return self._paginator
+
+    def paginate_queryset(self, queryset):
+        """
+        Return a single page of results, or `None` if pagination is disabled.
+        """
+        if self.paginator is None:
+            return None
+        return self.paginator.paginate_queryset(queryset, self.request, view=self)
+
+    def get_paginated_response(self, data):
+        """
+        Return a paginated style `Response` object for the given output data.
+        """
+        assert self.paginator is not None
+        return self.paginator.get_paginated_response(data)
+
+
+# Concrete view classes that provide method handlers
+# by composing the mixin classes with the base view.
+
+class CreateAPIView(mixins.CreateModelMixin,
+                    GenericAPIView):
+    """
+    Concrete view for creating a model instance.
+    """
+    def post(self, request, *args, **kwargs):
+        return self.create(request, *args, **kwargs)
+
+
+class ListAPIView(mixins.ListModelMixin,
+                  GenericAPIView):
+    """
+    Concrete view for listing a queryset.
+    """
+    def get(self, request, *args, **kwargs):
+        return self.list(request, *args, **kwargs)
+
+
+class RetrieveAPIView(mixins.RetrieveModelMixin,
+                      GenericAPIView):
+    """
+    Concrete view for retrieving a model instance.
+    """
+    def get(self, request, *args, **kwargs):
+        return self.retrieve(request, *args, **kwargs)
+
+
+class DestroyAPIView(mixins.DestroyModelMixin,
+                     GenericAPIView):
+    """
+    Concrete view for deleting a model instance.
+    """
+    def delete(self, request, *args, **kwargs):
+        return self.destroy(request, *args, **kwargs)
+
+
+class UpdateAPIView(mixins.UpdateModelMixin,
+                    GenericAPIView):
+    """
+    Concrete view for updating a model instance.
+    """
+    def put(self, request, *args, **kwargs):
+        return self.update(request, *args, **kwargs)
+
+    def patch(self, request, *args, **kwargs):
+        return self.partial_update(request, *args, **kwargs)
+
+
+class ListCreateAPIView(mixins.ListModelMixin,
+                        mixins.CreateModelMixin,
+                        GenericAPIView):
+    """
+    Concrete view for listing a queryset or creating a model instance.
+    """
+    def get(self, request, *args, **kwargs):
+        return self.list(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        return self.create(request, *args, **kwargs)
+
+
+class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
+                            mixins.UpdateModelMixin,
+                            GenericAPIView):
+    """
+    Concrete view for retrieving, updating a model instance.
+    """
+    def get(self, request, *args, **kwargs):
+        return self.retrieve(request, *args, **kwargs)
+
+    def put(self, request, *args, **kwargs):
+        return self.update(request, *args, **kwargs)
+
+    def patch(self, request, *args, **kwargs):
+        return self.partial_update(request, *args, **kwargs)
+
+
+class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
+                             mixins.DestroyModelMixin,
+                             GenericAPIView):
+    """
+    Concrete view for retrieving or deleting a model instance.
+    """
+    def get(self, request, *args, **kwargs):
+        return self.retrieve(request, *args, **kwargs)
+
+    def delete(self, request, *args, **kwargs):
+        return self.destroy(request, *args, **kwargs)
+
+
+class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
+                                   mixins.UpdateModelMixin,
+                                   mixins.DestroyModelMixin,
+                                   GenericAPIView):
+    """
+    Concrete view for retrieving, updating or deleting a model instance.
+    """
+    def get(self, request, *args, **kwargs):
+        return self.retrieve(request, *args, **kwargs)
+
+    def put(self, request, *args, **kwargs):
+        return self.update(request, *args, **kwargs)
+
+    def patch(self, request, *args, **kwargs):
+        return self.partial_update(request, *args, **kwargs)
+
+    def delete(self, request, *args, **kwargs):
+        return self.destroy(request, *args, **kwargs)

BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ach/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ar/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/be/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ca/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ca_ES/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/cs/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/da/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/de/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/el/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/el_GR/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en_AU/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en_CA/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/en_US/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/es/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/et/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fa/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fa_IR/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fi/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fr/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/fr_CA/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/gl/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/gl_ES/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/he_IL/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/hu/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/id/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/it/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ja/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/lv/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/mk/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/nb/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/nl/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/nn/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/no/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pl/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pt/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/pt_PT/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ro/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/ru/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/sk/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/sl/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/sv/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/tr/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/tr_TR/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/uk/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/vi/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_Hans/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_Hant/LC_MESSAGES/django.mo


BIN
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/locale/zh_TW/LC_MESSAGES/django.mo


+ 0 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/management/__init__.py


+ 0 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/management/commands/__init__.py


+ 41 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/management/commands/generateschema.py

@@ -0,0 +1,41 @@
+from django.core.management.base import BaseCommand
+
+from rest_framework.compat import coreapi
+from rest_framework.renderers import (
+    CoreJSONRenderer, JSONOpenAPIRenderer, OpenAPIRenderer
+)
+from rest_framework.schemas.generators import SchemaGenerator
+
+
+class Command(BaseCommand):
+    help = "Generates configured API schema for project."
+
+    def add_arguments(self, parser):
+        parser.add_argument('--title', dest="title", default=None, type=str)
+        parser.add_argument('--url', dest="url", default=None, type=str)
+        parser.add_argument('--description', dest="description", default=None, type=str)
+        parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json', 'corejson'], default='openapi', type=str)
+
+    def handle(self, *args, **options):
+        assert coreapi is not None, 'coreapi must be installed.'
+
+        generator = SchemaGenerator(
+            url=options['url'],
+            title=options['title'],
+            description=options['description']
+        )
+
+        schema = generator.get_schema(request=None, public=True)
+
+        renderer = self.get_renderer(options['format'])
+        output = renderer.render(schema, renderer_context={})
+        self.stdout.write(output.decode('utf-8'))
+
+    def get_renderer(self, format):
+        renderer_cls = {
+            'corejson': CoreJSONRenderer,
+            'openapi': OpenAPIRenderer,
+            'openapi-json': JSONOpenAPIRenderer,
+        }[format]
+
+        return renderer_cls()

+ 153 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/metadata.py

@@ -0,0 +1,153 @@
+"""
+The metadata API is used to allow customization of how `OPTIONS` requests
+are handled. We currently provide a single default implementation that returns
+some fairly ad-hoc information about the view.
+
+Future implementations might use JSON schema or other definitions in order
+to return this information in a more standardized way.
+"""
+from __future__ import unicode_literals
+
+from collections import OrderedDict
+
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
+from django.utils.encoding import force_text
+
+from rest_framework import exceptions, serializers
+from rest_framework.request import clone_request
+from rest_framework.utils.field_mapping import ClassLookupDict
+
+
+class BaseMetadata(object):
+    def determine_metadata(self, request, view):
+        """
+        Return a dictionary of metadata about the view.
+        Used to return responses for OPTIONS requests.
+        """
+        raise NotImplementedError(".determine_metadata() must be overridden.")
+
+
+class SimpleMetadata(BaseMetadata):
+    """
+    This is the default metadata implementation.
+    It returns an ad-hoc set of information about the view.
+    There are not any formalized standards for `OPTIONS` responses
+    for us to base this on.
+    """
+    label_lookup = ClassLookupDict({
+        serializers.Field: 'field',
+        serializers.BooleanField: 'boolean',
+        serializers.NullBooleanField: 'boolean',
+        serializers.CharField: 'string',
+        serializers.UUIDField: 'string',
+        serializers.URLField: 'url',
+        serializers.EmailField: 'email',
+        serializers.RegexField: 'regex',
+        serializers.SlugField: 'slug',
+        serializers.IntegerField: 'integer',
+        serializers.FloatField: 'float',
+        serializers.DecimalField: 'decimal',
+        serializers.DateField: 'date',
+        serializers.DateTimeField: 'datetime',
+        serializers.TimeField: 'time',
+        serializers.ChoiceField: 'choice',
+        serializers.MultipleChoiceField: 'multiple choice',
+        serializers.FileField: 'file upload',
+        serializers.ImageField: 'image upload',
+        serializers.ListField: 'list',
+        serializers.DictField: 'nested object',
+        serializers.Serializer: 'nested object',
+    })
+
+    def determine_metadata(self, request, view):
+        metadata = OrderedDict()
+        metadata['name'] = view.get_view_name()
+        metadata['description'] = view.get_view_description()
+        metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
+        metadata['parses'] = [parser.media_type for parser in view.parser_classes]
+        if hasattr(view, 'get_serializer'):
+            actions = self.determine_actions(request, view)
+            if actions:
+                metadata['actions'] = actions
+        return metadata
+
+    def determine_actions(self, request, view):
+        """
+        For generic class based views we return information about
+        the fields that are accepted for 'PUT' and 'POST' methods.
+        """
+        actions = {}
+        for method in {'PUT', 'POST'} & set(view.allowed_methods):
+            view.request = clone_request(request, method)
+            try:
+                # Test global permissions
+                if hasattr(view, 'check_permissions'):
+                    view.check_permissions(view.request)
+                # Test object permissions
+                if method == 'PUT' and hasattr(view, 'get_object'):
+                    view.get_object()
+            except (exceptions.APIException, PermissionDenied, Http404):
+                pass
+            else:
+                # If user has appropriate permissions for the view, include
+                # appropriate metadata about the fields that should be supplied.
+                serializer = view.get_serializer()
+                actions[method] = self.get_serializer_info(serializer)
+            finally:
+                view.request = request
+
+        return actions
+
+    def get_serializer_info(self, serializer):
+        """
+        Given an instance of a serializer, return a dictionary of metadata
+        about its fields.
+        """
+        if hasattr(serializer, 'child'):
+            # If this is a `ListSerializer` then we want to examine the
+            # underlying child serializer instance instead.
+            serializer = serializer.child
+        return OrderedDict([
+            (field_name, self.get_field_info(field))
+            for field_name, field in serializer.fields.items()
+            if not isinstance(field, serializers.HiddenField)
+        ])
+
+    def get_field_info(self, field):
+        """
+        Given an instance of a serializer field, return a dictionary
+        of metadata about it.
+        """
+        field_info = OrderedDict()
+        field_info['type'] = self.label_lookup[field]
+        field_info['required'] = getattr(field, 'required', False)
+
+        attrs = [
+            'read_only', 'label', 'help_text',
+            'min_length', 'max_length',
+            'min_value', 'max_value'
+        ]
+
+        for attr in attrs:
+            value = getattr(field, attr, None)
+            if value is not None and value != '':
+                field_info[attr] = force_text(value, strings_only=True)
+
+        if getattr(field, 'child', None):
+            field_info['child'] = self.get_field_info(field.child)
+        elif getattr(field, 'fields', None):
+            field_info['children'] = self.get_serializer_info(field)
+
+        if (not field_info.get('read_only') and
+            not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
+                hasattr(field, 'choices')):
+            field_info['choices'] = [
+                {
+                    'value': choice_value,
+                    'display_name': force_text(choice_name, strings_only=True)
+                }
+                for choice_value, choice_name in field.choices.items()
+            ]
+
+        return field_info

+ 97 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/mixins.py

@@ -0,0 +1,97 @@
+"""
+Basic building blocks for generic class based views.
+
+We don't bind behaviour to http method handlers yet,
+which allows mixin classes to be composed in interesting ways.
+"""
+from __future__ import unicode_literals
+
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.settings import api_settings
+
+
+class CreateModelMixin(object):
+    """
+    Create a model instance.
+    """
+    def create(self, request, *args, **kwargs):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        self.perform_create(serializer)
+        headers = self.get_success_headers(serializer.data)
+        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+    def perform_create(self, serializer):
+        serializer.save()
+
+    def get_success_headers(self, data):
+        try:
+            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
+        except (TypeError, KeyError):
+            return {}
+
+
+class ListModelMixin(object):
+    """
+    List a queryset.
+    """
+    def list(self, request, *args, **kwargs):
+        queryset = self.filter_queryset(self.get_queryset())
+
+        page = self.paginate_queryset(queryset)
+        if page is not None:
+            serializer = self.get_serializer(page, many=True)
+            return self.get_paginated_response(serializer.data)
+
+        serializer = self.get_serializer(queryset, many=True)
+        return Response(serializer.data)
+
+
+class RetrieveModelMixin(object):
+    """
+    Retrieve a model instance.
+    """
+    def retrieve(self, request, *args, **kwargs):
+        instance = self.get_object()
+        serializer = self.get_serializer(instance)
+        return Response(serializer.data)
+
+
+class UpdateModelMixin(object):
+    """
+    Update a model instance.
+    """
+    def update(self, request, *args, **kwargs):
+        partial = kwargs.pop('partial', False)
+        instance = self.get_object()
+        serializer = self.get_serializer(instance, data=request.data, partial=partial)
+        serializer.is_valid(raise_exception=True)
+        self.perform_update(serializer)
+
+        if getattr(instance, '_prefetched_objects_cache', None):
+            # If 'prefetch_related' has been applied to a queryset, we need to
+            # forcibly invalidate the prefetch cache on the instance.
+            instance._prefetched_objects_cache = {}
+
+        return Response(serializer.data)
+
+    def perform_update(self, serializer):
+        serializer.save()
+
+    def partial_update(self, request, *args, **kwargs):
+        kwargs['partial'] = True
+        return self.update(request, *args, **kwargs)
+
+
+class DestroyModelMixin(object):
+    """
+    Destroy a model instance.
+    """
+    def destroy(self, request, *args, **kwargs):
+        instance = self.get_object()
+        self.perform_destroy(instance)
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+    def perform_destroy(self, instance):
+        instance.delete()

+ 97 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/negotiation.py

@@ -0,0 +1,97 @@
+"""
+Content negotiation deals with selecting an appropriate renderer given the
+incoming request.  Typically this will be based on the request's Accept header.
+"""
+from __future__ import unicode_literals
+
+from django.http import Http404
+
+from rest_framework import HTTP_HEADER_ENCODING, exceptions
+from rest_framework.settings import api_settings
+from rest_framework.utils.mediatypes import (
+    _MediaType, media_type_matches, order_by_precedence
+)
+
+
+class BaseContentNegotiation(object):
+    def select_parser(self, request, parsers):
+        raise NotImplementedError('.select_parser() must be implemented')
+
+    def select_renderer(self, request, renderers, format_suffix=None):
+        raise NotImplementedError('.select_renderer() must be implemented')
+
+
+class DefaultContentNegotiation(BaseContentNegotiation):
+    settings = api_settings
+
+    def select_parser(self, request, parsers):
+        """
+        Given a list of parsers and a media type, return the appropriate
+        parser to handle the incoming request.
+        """
+        for parser in parsers:
+            if media_type_matches(parser.media_type, request.content_type):
+                return parser
+        return None
+
+    def select_renderer(self, request, renderers, format_suffix=None):
+        """
+        Given a request and a list of renderers, return a two-tuple of:
+        (renderer, media type).
+        """
+        # Allow URL style format override.  eg. "?format=json
+        format_query_param = self.settings.URL_FORMAT_OVERRIDE
+        format = format_suffix or request.query_params.get(format_query_param)
+
+        if format:
+            renderers = self.filter_renderers(renderers, format)
+
+        accepts = self.get_accept_list(request)
+
+        # Check the acceptable media types against each renderer,
+        # attempting more specific media types first
+        # NB. The inner loop here isn't as bad as it first looks :)
+        #     Worst case is we're looping over len(accept_list) * len(self.renderers)
+        for media_type_set in order_by_precedence(accepts):
+            for renderer in renderers:
+                for media_type in media_type_set:
+                    if media_type_matches(renderer.media_type, media_type):
+                        # Return the most specific media type as accepted.
+                        media_type_wrapper = _MediaType(media_type)
+                        if (
+                            _MediaType(renderer.media_type).precedence >
+                            media_type_wrapper.precedence
+                        ):
+                            # Eg client requests '*/*'
+                            # Accepted media type is 'application/json'
+                            full_media_type = ';'.join(
+                                (renderer.media_type,) +
+                                tuple('{0}={1}'.format(
+                                    key, value.decode(HTTP_HEADER_ENCODING))
+                                    for key, value in media_type_wrapper.params.items()))
+                            return renderer, full_media_type
+                        else:
+                            # Eg client requests 'application/json; indent=8'
+                            # Accepted media type is 'application/json; indent=8'
+                            return renderer, media_type
+
+        raise exceptions.NotAcceptable(available_renderers=renderers)
+
+    def filter_renderers(self, renderers, format):
+        """
+        If there is a '.json' style format suffix, filter the renderers
+        so that we only negotiation against those that accept that format.
+        """
+        renderers = [renderer for renderer in renderers
+                     if renderer.format == format]
+        if not renderers:
+            raise Http404
+        return renderers
+
+    def get_accept_list(self, request):
+        """
+        Given the incoming request, return a tokenized list of media
+        type strings.
+        """
+        header = request.META.get('HTTP_ACCEPT', '*/*')
+        return [token.strip() for token in header.split(',')]

+ 822 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/pagination.py

@@ -0,0 +1,822 @@
+# coding: utf-8
+"""
+Pagination serializers determine the structure of the output that should
+be used for paginated responses.
+"""
+from __future__ import unicode_literals
+
+from base64 import b64decode, b64encode
+from collections import OrderedDict, namedtuple
+
+from django.core.paginator import InvalidPage
+from django.core.paginator import Paginator as DjangoPaginator
+from django.template import loader
+from django.utils import six
+from django.utils.encoding import force_text
+from django.utils.six.moves.urllib import parse as urlparse
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework.compat import coreapi, coreschema
+from rest_framework.exceptions import NotFound
+from rest_framework.response import Response
+from rest_framework.settings import api_settings
+from rest_framework.utils.urls import remove_query_param, replace_query_param
+
+
+def _positive_int(integer_string, strict=False, cutoff=None):
+    """
+    Cast a string to a strictly positive integer.
+    """
+    ret = int(integer_string)
+    if ret < 0 or (ret == 0 and strict):
+        raise ValueError()
+    if cutoff:
+        return min(ret, cutoff)
+    return ret
+
+
+def _divide_with_ceil(a, b):
+    """
+    Returns 'a' divided by 'b', with any remainder rounded up.
+    """
+    if a % b:
+        return (a // b) + 1
+
+    return a // b
+
+
+def _get_displayed_page_numbers(current, final):
+    """
+    This utility function determines a list of page numbers to display.
+    This gives us a nice contextually relevant set of page numbers.
+
+    For example:
+    current=14, final=16 -> [1, None, 13, 14, 15, 16]
+
+    This implementation gives one page to each side of the cursor,
+    or two pages to the side when the cursor is at the edge, then
+    ensures that any breaks between non-continuous page numbers never
+    remove only a single page.
+
+    For an alternative implementation which gives two pages to each side of
+    the cursor, eg. as in GitHub issue list pagination, see:
+
+    https://gist.github.com/tomchristie/321140cebb1c4a558b15
+    """
+    assert current >= 1
+    assert final >= current
+
+    if final <= 5:
+        return list(range(1, final + 1))
+
+    # We always include the first two pages, last two pages, and
+    # two pages either side of the current page.
+    included = {1, current - 1, current, current + 1, final}
+
+    # If the break would only exclude a single page number then we
+    # may as well include the page number instead of the break.
+    if current <= 4:
+        included.add(2)
+        included.add(3)
+    if current >= final - 3:
+        included.add(final - 1)
+        included.add(final - 2)
+
+    # Now sort the page numbers and drop anything outside the limits.
+    included = [
+        idx for idx in sorted(list(included))
+        if 0 < idx <= final
+    ]
+
+    # Finally insert any `...` breaks
+    if current > 4:
+        included.insert(1, None)
+    if current < final - 3:
+        included.insert(len(included) - 1, None)
+    return included
+
+
+def _get_page_links(page_numbers, current, url_func):
+    """
+    Given a list of page numbers and `None` page breaks,
+    return a list of `PageLink` objects.
+    """
+    page_links = []
+    for page_number in page_numbers:
+        if page_number is None:
+            page_link = PAGE_BREAK
+        else:
+            page_link = PageLink(
+                url=url_func(page_number),
+                number=page_number,
+                is_active=(page_number == current),
+                is_break=False
+            )
+        page_links.append(page_link)
+    return page_links
+
+
+def _reverse_ordering(ordering_tuple):
+    """
+    Given an order_by tuple such as `('-created', 'uuid')` reverse the
+    ordering and return a new tuple, eg. `('created', '-uuid')`.
+    """
+    def invert(x):
+        return x[1:] if x.startswith('-') else '-' + x
+
+    return tuple([invert(item) for item in ordering_tuple])
+
+
+Cursor = namedtuple('Cursor', ['offset', 'reverse', 'position'])
+PageLink = namedtuple('PageLink', ['url', 'number', 'is_active', 'is_break'])
+
+PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True)
+
+
+class BasePagination(object):
+    display_page_controls = False
+
+    def paginate_queryset(self, queryset, request, view=None):  # pragma: no cover
+        raise NotImplementedError('paginate_queryset() must be implemented.')
+
+    def get_paginated_response(self, data):  # pragma: no cover
+        raise NotImplementedError('get_paginated_response() must be implemented.')
+
+    def to_html(self):  # pragma: no cover
+        raise NotImplementedError('to_html() must be implemented to display page controls.')
+
+    def get_results(self, data):
+        return data['results']
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        return []
+
+
+class PageNumberPagination(BasePagination):
+    """
+    A simple page number based style that supports page numbers as
+    query parameters. For example:
+
+    http://api.example.org/accounts/?page=4
+    http://api.example.org/accounts/?page=4&page_size=100
+    """
+    # The default page size.
+    # Defaults to `None`, meaning pagination is disabled.
+    page_size = api_settings.PAGE_SIZE
+
+    django_paginator_class = DjangoPaginator
+
+    # Client can control the page using this query parameter.
+    page_query_param = 'page'
+    page_query_description = _('A page number within the paginated result set.')
+
+    # Client can control the page size using this query parameter.
+    # Default is 'None'. Set to eg 'page_size' to enable usage.
+    page_size_query_param = None
+    page_size_query_description = _('Number of results to return per page.')
+
+    # Set to an integer to limit the maximum page size the client may request.
+    # Only relevant if 'page_size_query_param' has also been set.
+    max_page_size = None
+
+    last_page_strings = ('last',)
+
+    template = 'rest_framework/pagination/numbers.html'
+
+    invalid_page_message = _('Invalid page.')
+
+    def paginate_queryset(self, queryset, request, view=None):
+        """
+        Paginate a queryset if required, either returning a
+        page object, or `None` if pagination is not configured for this view.
+        """
+        page_size = self.get_page_size(request)
+        if not page_size:
+            return None
+
+        paginator = self.django_paginator_class(queryset, page_size)
+        page_number = request.query_params.get(self.page_query_param, 1)
+        if page_number in self.last_page_strings:
+            page_number = paginator.num_pages
+
+        try:
+            self.page = paginator.page(page_number)
+        except InvalidPage as exc:
+            msg = self.invalid_page_message.format(
+                page_number=page_number, message=six.text_type(exc)
+            )
+            raise NotFound(msg)
+
+        if paginator.num_pages > 1 and self.template is not None:
+            # The browsable API should display pagination controls.
+            self.display_page_controls = True
+
+        self.request = request
+        return list(self.page)
+
+    def get_paginated_response(self, data):
+        return Response(OrderedDict([
+            ('count', self.page.paginator.count),
+            ('next', self.get_next_link()),
+            ('previous', self.get_previous_link()),
+            ('results', data)
+        ]))
+
+    def get_page_size(self, request):
+        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 self.page_size
+
+    def get_next_link(self):
+        if not self.page.has_next():
+            return None
+        url = self.request.build_absolute_uri()
+        page_number = self.page.next_page_number()
+        return replace_query_param(url, self.page_query_param, page_number)
+
+    def get_previous_link(self):
+        if not self.page.has_previous():
+            return None
+        url = self.request.build_absolute_uri()
+        page_number = self.page.previous_page_number()
+        if page_number == 1:
+            return remove_query_param(url, self.page_query_param)
+        return replace_query_param(url, self.page_query_param, page_number)
+
+    def get_html_context(self):
+        base_url = self.request.build_absolute_uri()
+
+        def page_number_to_url(page_number):
+            if page_number == 1:
+                return remove_query_param(base_url, self.page_query_param)
+            else:
+                return replace_query_param(base_url, self.page_query_param, page_number)
+
+        current = self.page.number
+        final = self.page.paginator.num_pages
+        page_numbers = _get_displayed_page_numbers(current, final)
+        page_links = _get_page_links(page_numbers, current, page_number_to_url)
+
+        return {
+            'previous_url': self.get_previous_link(),
+            'next_url': self.get_next_link(),
+            'page_links': page_links
+        }
+
+    def to_html(self):
+        template = loader.get_template(self.template)
+        context = self.get_html_context()
+        return template.render(context)
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
+        fields = [
+            coreapi.Field(
+                name=self.page_query_param,
+                required=False,
+                location='query',
+                schema=coreschema.Integer(
+                    title='Page',
+                    description=force_text(self.page_query_description)
+                )
+            )
+        ]
+        if self.page_size_query_param is not None:
+            fields.append(
+                coreapi.Field(
+                    name=self.page_size_query_param,
+                    required=False,
+                    location='query',
+                    schema=coreschema.Integer(
+                        title='Page size',
+                        description=force_text(self.page_size_query_description)
+                    )
+                )
+            )
+        return fields
+
+
+class LimitOffsetPagination(BasePagination):
+    """
+    A limit/offset based style. For example:
+
+    http://api.example.org/accounts/?limit=100
+    http://api.example.org/accounts/?offset=400&limit=100
+    """
+    default_limit = api_settings.PAGE_SIZE
+    limit_query_param = 'limit'
+    limit_query_description = _('Number of results to return per page.')
+    offset_query_param = 'offset'
+    offset_query_description = _('The initial index from which to return the results.')
+    max_limit = None
+    template = 'rest_framework/pagination/numbers.html'
+
+    def paginate_queryset(self, queryset, request, view=None):
+        self.count = self.get_count(queryset)
+        self.limit = self.get_limit(request)
+        if self.limit is None:
+            return None
+
+        self.offset = self.get_offset(request)
+        self.request = request
+        if self.count > self.limit and self.template is not None:
+            self.display_page_controls = True
+
+        if self.count == 0 or self.offset > self.count:
+            return []
+        return list(queryset[self.offset:self.offset + self.limit])
+
+    def get_paginated_response(self, data):
+        return Response(OrderedDict([
+            ('count', self.count),
+            ('next', self.get_next_link()),
+            ('previous', self.get_previous_link()),
+            ('results', data)
+        ]))
+
+    def get_limit(self, request):
+        if self.limit_query_param:
+            try:
+                return _positive_int(
+                    request.query_params[self.limit_query_param],
+                    strict=True,
+                    cutoff=self.max_limit
+                )
+            except (KeyError, ValueError):
+                pass
+
+        return self.default_limit
+
+    def get_offset(self, request):
+        try:
+            return _positive_int(
+                request.query_params[self.offset_query_param],
+            )
+        except (KeyError, ValueError):
+            return 0
+
+    def get_next_link(self):
+        if self.offset + self.limit >= self.count:
+            return None
+
+        url = self.request.build_absolute_uri()
+        url = replace_query_param(url, self.limit_query_param, self.limit)
+
+        offset = self.offset + self.limit
+        return replace_query_param(url, self.offset_query_param, offset)
+
+    def get_previous_link(self):
+        if self.offset <= 0:
+            return None
+
+        url = self.request.build_absolute_uri()
+        url = replace_query_param(url, self.limit_query_param, self.limit)
+
+        if self.offset - self.limit <= 0:
+            return remove_query_param(url, self.offset_query_param)
+
+        offset = self.offset - self.limit
+        return replace_query_param(url, self.offset_query_param, offset)
+
+    def get_html_context(self):
+        base_url = self.request.build_absolute_uri()
+
+        if self.limit:
+            current = _divide_with_ceil(self.offset, self.limit) + 1
+
+            # The number of pages is a little bit fiddly.
+            # We need to sum both the number of pages from current offset to end
+            # plus the number of pages up to the current offset.
+            # When offset is not strictly divisible by the limit then we may
+            # end up introducing an extra page as an artifact.
+            final = (
+                _divide_with_ceil(self.count - self.offset, self.limit) +
+                _divide_with_ceil(self.offset, self.limit)
+            )
+
+            if final < 1:
+                final = 1
+        else:
+            current = 1
+            final = 1
+
+        if current > final:
+            current = final
+
+        def page_number_to_url(page_number):
+            if page_number == 1:
+                return remove_query_param(base_url, self.offset_query_param)
+            else:
+                offset = self.offset + ((page_number - current) * self.limit)
+                return replace_query_param(base_url, self.offset_query_param, offset)
+
+        page_numbers = _get_displayed_page_numbers(current, final)
+        page_links = _get_page_links(page_numbers, current, page_number_to_url)
+
+        return {
+            'previous_url': self.get_previous_link(),
+            'next_url': self.get_next_link(),
+            'page_links': page_links
+        }
+
+    def to_html(self):
+        template = loader.get_template(self.template)
+        context = self.get_html_context()
+        return template.render(context)
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
+        return [
+            coreapi.Field(
+                name=self.limit_query_param,
+                required=False,
+                location='query',
+                schema=coreschema.Integer(
+                    title='Limit',
+                    description=force_text(self.limit_query_description)
+                )
+            ),
+            coreapi.Field(
+                name=self.offset_query_param,
+                required=False,
+                location='query',
+                schema=coreschema.Integer(
+                    title='Offset',
+                    description=force_text(self.offset_query_description)
+                )
+            )
+        ]
+
+    def get_count(self, queryset):
+        """
+        Determine an object count, supporting either querysets or regular lists.
+        """
+        try:
+            return queryset.count()
+        except (AttributeError, TypeError):
+            return len(queryset)
+
+
+class CursorPagination(BasePagination):
+    """
+    The cursor pagination implementation is necessarily complex.
+    For an overview of the position/offset style we use, see this post:
+    https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
+    """
+    cursor_query_param = 'cursor'
+    cursor_query_description = _('The pagination cursor value.')
+    page_size = api_settings.PAGE_SIZE
+    invalid_cursor_message = _('Invalid cursor')
+    ordering = '-created'
+    template = 'rest_framework/pagination/previous_and_next.html'
+
+    # Client can control the page size using this query parameter.
+    # Default is 'None'. Set to eg 'page_size' to enable usage.
+    page_size_query_param = None
+    page_size_query_description = _('Number of results to return per page.')
+
+    # Set to an integer to limit the maximum page size the client may request.
+    # Only relevant if 'page_size_query_param' has also been set.
+    max_page_size = None
+
+    # The offset in the cursor is used in situations where we have a
+    # nearly-unique index. (Eg millisecond precision creation timestamps)
+    # We guard against malicious users attempting to cause expensive database
+    # queries, by having a hard cap on the maximum possible size of the offset.
+    offset_cutoff = 1000
+
+    def paginate_queryset(self, queryset, request, view=None):
+        self.page_size = self.get_page_size(request)
+        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 reverse:
+            # 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.
+            self.page = list(reversed(self.page))
+
+            # 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
+
+    def get_page_size(self, request):
+        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 self.page_size
+
+    def get_next_link(self):
+        if not self.has_next:
+            return None
+
+        if self.cursor and self.cursor.reverse and self.cursor.offset != 0:
+            # If we're reversing direction and we have an offset cursor
+            # then we cannot use the first position we find as a marker.
+            compare = self._get_position_from_instance(self.page[-1], self.ordering)
+        else:
+            compare = self.next_position
+        offset = 0
+
+        for item in reversed(self.page):
+            position = self._get_position_from_instance(item, self.ordering)
+            if position != compare:
+                # The item in this position and the item following it
+                # have different positions. We can use this position as
+                # our marker.
+                break
+
+            # The item in this position has the same position as the item
+            # following it, we can't use it as a marker position, so increment
+            # the offset and keep seeking to the previous item.
+            compare = position
+            offset += 1
+
+        else:
+            # There were no unique positions in the page.
+            if not self.has_previous:
+                # We are on the first page.
+                # Our cursor will have an offset equal to the page size,
+                # but no position to filter against yet.
+                offset = self.page_size
+                position = None
+            elif self.cursor.reverse:
+                # The change in direction will introduce a paging artifact,
+                # where we end up skipping forward a few extra items.
+                offset = 0
+                position = self.previous_position
+            else:
+                # Use the position from the existing cursor and increment
+                # it's offset by the page size.
+                offset = self.cursor.offset + self.page_size
+                position = self.previous_position
+
+        cursor = Cursor(offset=offset, reverse=False, position=position)
+        return self.encode_cursor(cursor)
+
+    def get_previous_link(self):
+        if not self.has_previous:
+            return None
+
+        if self.cursor and not self.cursor.reverse and self.cursor.offset != 0:
+            # If we're reversing direction and we have an offset cursor
+            # then we cannot use the first position we find as a marker.
+            compare = self._get_position_from_instance(self.page[0], self.ordering)
+        else:
+            compare = self.previous_position
+        offset = 0
+
+        for item in self.page:
+            position = self._get_position_from_instance(item, self.ordering)
+            if position != compare:
+                # The item in this position and the item following it
+                # have different positions. We can use this position as
+                # our marker.
+                break
+
+            # The item in this position has the same position as the item
+            # following it, we can't use it as a marker position, so increment
+            # the offset and keep seeking to the previous item.
+            compare = position
+            offset += 1
+
+        else:
+            # There were no unique positions in the page.
+            if not self.has_next:
+                # We are on the final page.
+                # Our cursor will have an offset equal to the page size,
+                # but no position to filter against yet.
+                offset = self.page_size
+                position = None
+            elif self.cursor.reverse:
+                # Use the position from the existing cursor and increment
+                # it's offset by the page size.
+                offset = self.cursor.offset + self.page_size
+                position = self.next_position
+            else:
+                # The change in direction will introduce a paging artifact,
+                # where we end up skipping back a few extra items.
+                offset = 0
+                position = self.next_position
+
+        cursor = Cursor(offset=offset, reverse=True, position=position)
+        return self.encode_cursor(cursor)
+
+    def get_ordering(self, request, queryset, view):
+        """
+        Return a tuple of strings, that may be used in an `order_by` method.
+        """
+        ordering_filters = [
+            filter_cls for filter_cls in getattr(view, 'filter_backends', [])
+            if hasattr(filter_cls, 'get_ordering')
+        ]
+
+        if ordering_filters:
+            # If a filter exists on the view that implements `get_ordering`
+            # then we defer to that filter to determine the ordering.
+            filter_cls = ordering_filters[0]
+            filter_instance = filter_cls()
+            ordering = filter_instance.get_ordering(request, queryset, view)
+            assert ordering is not None, (
+                'Using cursor pagination, but filter class {filter_cls} '
+                'returned a `None` ordering.'.format(
+                    filter_cls=filter_cls.__name__
+                )
+            )
+        else:
+            # The default case is to check for an `ordering` attribute
+            # on this pagination instance.
+            ordering = self.ordering
+            assert ordering is not None, (
+                'Using cursor pagination, but no ordering attribute was declared '
+                'on the pagination class.'
+            )
+            assert '__' not in ordering, (
+                'Cursor pagination does not support double underscore lookups '
+                'for orderings. Orderings should be an unchanging, unique or '
+                'nearly-unique field on the model, such as "-created" or "pk".'
+            )
+
+        assert isinstance(ordering, (six.string_types, list, tuple)), (
+            'Invalid ordering. Expected string or tuple, but got {type}'.format(
+                type=type(ordering).__name__
+            )
+        )
+
+        if isinstance(ordering, six.string_types):
+            return (ordering,)
+        return tuple(ordering)
+
+    def decode_cursor(self, request):
+        """
+        Given a request with a cursor, return a `Cursor` instance.
+        """
+        # Determine if we have a cursor, and if so then decode it.
+        encoded = request.query_params.get(self.cursor_query_param)
+        if encoded is None:
+            return None
+
+        try:
+            querystring = b64decode(encoded.encode('ascii')).decode('ascii')
+            tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
+
+            offset = tokens.get('o', ['0'])[0]
+            offset = _positive_int(offset, cutoff=self.offset_cutoff)
+
+            reverse = tokens.get('r', ['0'])[0]
+            reverse = bool(int(reverse))
+
+            position = tokens.get('p', [None])[0]
+        except (TypeError, ValueError):
+            raise NotFound(self.invalid_cursor_message)
+
+        return Cursor(offset=offset, reverse=reverse, position=position)
+
+    def encode_cursor(self, cursor):
+        """
+        Given a Cursor instance, return an url with encoded cursor.
+        """
+        tokens = {}
+        if cursor.offset != 0:
+            tokens['o'] = str(cursor.offset)
+        if cursor.reverse:
+            tokens['r'] = '1'
+        if cursor.position is not None:
+            tokens['p'] = cursor.position
+
+        querystring = urlparse.urlencode(tokens, doseq=True)
+        encoded = b64encode(querystring.encode('ascii')).decode('ascii')
+        return replace_query_param(self.base_url, self.cursor_query_param, encoded)
+
+    def _get_position_from_instance(self, instance, ordering):
+        field_name = ordering[0].lstrip('-')
+        if isinstance(instance, dict):
+            attr = instance[field_name]
+        else:
+            attr = getattr(instance, field_name)
+        return six.text_type(attr)
+
+    def get_paginated_response(self, data):
+        return Response(OrderedDict([
+            ('next', self.get_next_link()),
+            ('previous', self.get_previous_link()),
+            ('results', data)
+        ]))
+
+    def get_html_context(self):
+        return {
+            'previous_url': self.get_previous_link(),
+            'next_url': self.get_next_link()
+        }
+
+    def to_html(self):
+        template = loader.get_template(self.template)
+        context = self.get_html_context()
+        return template.render(context)
+
+    def get_schema_fields(self, view):
+        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
+        fields = [
+            coreapi.Field(
+                name=self.cursor_query_param,
+                required=False,
+                location='query',
+                schema=coreschema.String(
+                    title='Cursor',
+                    description=force_text(self.cursor_query_description)
+                )
+            )
+        ]
+        if self.page_size_query_param is not None:
+            fields.append(
+                coreapi.Field(
+                    name=self.page_size_query_param,
+                    required=False,
+                    location='query',
+                    schema=coreschema.Integer(
+                        title='Page size',
+                        description=force_text(self.page_size_query_description)
+                    )
+                )
+            )
+        return fields

+ 227 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/parsers.py

@@ -0,0 +1,227 @@
+"""
+Parsers are used to parse the content of incoming HTTP requests.
+
+They give us a generic way of being able to handle various media types
+on the request, such as form content or json encoded data.
+"""
+from __future__ import unicode_literals
+
+import codecs
+
+from django.conf import settings
+from django.core.files.uploadhandler import StopFutureHandlers
+from django.http import QueryDict
+from django.http.multipartparser import ChunkIter
+from django.http.multipartparser import \
+    MultiPartParser as DjangoMultiPartParser
+from django.http.multipartparser import MultiPartParserError, parse_header
+from django.utils import six
+from django.utils.encoding import force_text
+from django.utils.six.moves.urllib import parse as urlparse
+
+from rest_framework import renderers
+from rest_framework.exceptions import ParseError
+from rest_framework.settings import api_settings
+from rest_framework.utils import json
+
+
+class DataAndFiles(object):
+    def __init__(self, data, files):
+        self.data = data
+        self.files = files
+
+
+class BaseParser(object):
+    """
+    All parsers should extend `BaseParser`, specifying a `media_type`
+    attribute, and overriding the `.parse()` method.
+    """
+    media_type = None
+
+    def parse(self, stream, media_type=None, parser_context=None):
+        """
+        Given a stream to read from, return the parsed representation.
+        Should return parsed data, or a `DataAndFiles` object consisting of the
+        parsed data and files.
+        """
+        raise NotImplementedError(".parse() must be overridden.")
+
+
+class JSONParser(BaseParser):
+    """
+    Parses JSON-serialized data.
+    """
+    media_type = 'application/json'
+    renderer_class = renderers.JSONRenderer
+    strict = api_settings.STRICT_JSON
+
+    def parse(self, stream, media_type=None, parser_context=None):
+        """
+        Parses the incoming bytestream as JSON and returns the resulting data.
+        """
+        parser_context = parser_context or {}
+        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+
+        try:
+            decoded_stream = codecs.getreader(encoding)(stream)
+            parse_constant = json.strict_constant if self.strict else None
+            return json.load(decoded_stream, parse_constant=parse_constant)
+        except ValueError as exc:
+            raise ParseError('JSON parse error - %s' % six.text_type(exc))
+
+
+class FormParser(BaseParser):
+    """
+    Parser for form data.
+    """
+    media_type = 'application/x-www-form-urlencoded'
+
+    def parse(self, stream, media_type=None, parser_context=None):
+        """
+        Parses the incoming bytestream as a URL encoded form,
+        and returns the resulting QueryDict.
+        """
+        parser_context = parser_context or {}
+        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+        data = QueryDict(stream.read(), encoding=encoding)
+        return data
+
+
+class MultiPartParser(BaseParser):
+    """
+    Parser for multipart form data, which may include file data.
+    """
+    media_type = 'multipart/form-data'
+
+    def parse(self, stream, media_type=None, parser_context=None):
+        """
+        Parses the incoming bytestream as a multipart encoded form,
+        and returns a DataAndFiles object.
+
+        `.data` will be a `QueryDict` containing all the form parameters.
+        `.files` will be a `QueryDict` containing all the form files.
+        """
+        parser_context = parser_context or {}
+        request = parser_context['request']
+        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+        meta = request.META.copy()
+        meta['CONTENT_TYPE'] = media_type
+        upload_handlers = request.upload_handlers
+
+        try:
+            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding)
+            data, files = parser.parse()
+            return DataAndFiles(data, files)
+        except MultiPartParserError as exc:
+            raise ParseError('Multipart form parse error - %s' % six.text_type(exc))
+
+
+class FileUploadParser(BaseParser):
+    """
+    Parser for file upload data.
+    """
+    media_type = '*/*'
+    errors = {
+        'unhandled': 'FileUpload parse error - none of upload handlers can handle the stream',
+        'no_filename': 'Missing filename. Request should include a Content-Disposition header with a filename parameter.',
+    }
+
+    def parse(self, stream, media_type=None, parser_context=None):
+        """
+        Treats the incoming bytestream as a raw file upload and returns
+        a `DataAndFiles` object.
+
+        `.data` will be None (we expect request body to be a file content).
+        `.files` will be a `QueryDict` containing one 'file' element.
+        """
+        parser_context = parser_context or {}
+        request = parser_context['request']
+        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+        meta = request.META
+        upload_handlers = request.upload_handlers
+        filename = self.get_filename(stream, media_type, parser_context)
+
+        if not filename:
+            raise ParseError(self.errors['no_filename'])
+
+        # Note that this code is extracted from Django's handling of
+        # file uploads in MultiPartParser.
+        content_type = meta.get('HTTP_CONTENT_TYPE',
+                                meta.get('CONTENT_TYPE', ''))
+        try:
+            content_length = int(meta.get('HTTP_CONTENT_LENGTH',
+                                          meta.get('CONTENT_LENGTH', 0)))
+        except (ValueError, TypeError):
+            content_length = None
+
+        # See if the handler will want to take care of the parsing.
+        for handler in upload_handlers:
+            result = handler.handle_raw_input(stream,
+                                              meta,
+                                              content_length,
+                                              None,
+                                              encoding)
+            if result is not None:
+                return DataAndFiles({}, {'file': result[1]})
+
+        # This is the standard case.
+        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
+        chunk_size = min([2 ** 31 - 4] + possible_sizes)
+        chunks = ChunkIter(stream, chunk_size)
+        counters = [0] * len(upload_handlers)
+
+        for index, handler in enumerate(upload_handlers):
+            try:
+                handler.new_file(None, filename, content_type,
+                                 content_length, encoding)
+            except StopFutureHandlers:
+                upload_handlers = upload_handlers[:index + 1]
+                break
+
+        for chunk in chunks:
+            for index, handler in enumerate(upload_handlers):
+                chunk_length = len(chunk)
+                chunk = handler.receive_data_chunk(chunk, counters[index])
+                counters[index] += chunk_length
+                if chunk is None:
+                    break
+
+        for index, handler in enumerate(upload_handlers):
+            file_obj = handler.file_complete(counters[index])
+            if file_obj is not None:
+                return DataAndFiles({}, {'file': file_obj})
+
+        raise ParseError(self.errors['unhandled'])
+
+    def get_filename(self, stream, media_type, parser_context):
+        """
+        Detects the uploaded file name. First searches a 'filename' url kwarg.
+        Then tries to parse Content-Disposition header.
+        """
+        try:
+            return parser_context['kwargs']['filename']
+        except KeyError:
+            pass
+
+        try:
+            meta = parser_context['request'].META
+            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))
+            filename_parm = disposition[1]
+            if 'filename*' in filename_parm:
+                return self.get_encoded_filename(filename_parm)
+            return force_text(filename_parm['filename'])
+        except (AttributeError, KeyError, ValueError):
+            pass
+
+    def get_encoded_filename(self, filename_parm):
+        """
+        Handle encoded filenames per RFC6266. See also:
+        https://tools.ietf.org/html/rfc2231#section-4
+        """
+        encoded_filename = force_text(filename_parm['filename*'])
+        try:
+            charset, lang, filename = encoded_filename.split('\'', 2)
+            filename = urlparse.unquote(filename)
+        except (ValueError, LookupError):
+            filename = force_text(filename_parm['filename'])
+        return filename

+ 304 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/permissions.py

@@ -0,0 +1,304 @@
+"""
+Provides a set of pluggable permission policies.
+"""
+from __future__ import unicode_literals
+
+from django.http import Http404
+from django.utils import six
+
+from rest_framework import exceptions
+
+SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
+
+
+class OperationHolderMixin:
+    def __and__(self, other):
+        return OperandHolder(AND, self, other)
+
+    def __or__(self, other):
+        return OperandHolder(OR, self, other)
+
+    def __rand__(self, other):
+        return OperandHolder(AND, other, self)
+
+    def __ror__(self, other):
+        return OperandHolder(OR, other, self)
+
+    def __invert__(self):
+        return SingleOperandHolder(NOT, self)
+
+
+class SingleOperandHolder(OperationHolderMixin):
+    def __init__(self, operator_class, op1_class):
+        self.operator_class = operator_class
+        self.op1_class = op1_class
+
+    def __call__(self, *args, **kwargs):
+        op1 = self.op1_class(*args, **kwargs)
+        return self.operator_class(op1)
+
+
+class OperandHolder(OperationHolderMixin):
+    def __init__(self, operator_class, op1_class, op2_class):
+        self.operator_class = operator_class
+        self.op1_class = op1_class
+        self.op2_class = op2_class
+
+    def __call__(self, *args, **kwargs):
+        op1 = self.op1_class(*args, **kwargs)
+        op2 = self.op2_class(*args, **kwargs)
+        return self.operator_class(op1, op2)
+
+
+class AND:
+    def __init__(self, op1, op2):
+        self.op1 = op1
+        self.op2 = op2
+
+    def has_permission(self, request, view):
+        return (
+            self.op1.has_permission(request, view) and
+            self.op2.has_permission(request, view)
+        )
+
+    def has_object_permission(self, request, view, obj):
+        return (
+            self.op1.has_object_permission(request, view, obj) and
+            self.op2.has_object_permission(request, view, obj)
+        )
+
+
+class OR:
+    def __init__(self, op1, op2):
+        self.op1 = op1
+        self.op2 = op2
+
+    def has_permission(self, request, view):
+        return (
+            self.op1.has_permission(request, view) or
+            self.op2.has_permission(request, view)
+        )
+
+    def has_object_permission(self, request, view, obj):
+        return (
+            self.op1.has_object_permission(request, view, obj) or
+            self.op2.has_object_permission(request, view, obj)
+        )
+
+
+class NOT:
+    def __init__(self, op1):
+        self.op1 = op1
+
+    def has_permission(self, request, view):
+        return not self.op1.has_permission(request, view)
+
+    def has_object_permission(self, request, view, obj):
+        return not self.op1.has_object_permission(request, view, obj)
+
+
+class BasePermissionMetaclass(OperationHolderMixin, type):
+    pass
+
+
+@six.add_metaclass(BasePermissionMetaclass)
+class BasePermission(object):
+    """
+    A base class from which all permission classes should inherit.
+    """
+
+    def has_permission(self, request, view):
+        """
+        Return `True` if permission is granted, `False` otherwise.
+        """
+        return True
+
+    def has_object_permission(self, request, view, obj):
+        """
+        Return `True` if permission is granted, `False` otherwise.
+        """
+        return True
+
+
+class AllowAny(BasePermission):
+    """
+    Allow any access.
+    This isn't strictly required, since you could use an empty
+    permission_classes list, but it's useful because it makes the intention
+    more explicit.
+    """
+
+    def has_permission(self, request, view):
+        return True
+
+
+class IsAuthenticated(BasePermission):
+    """
+    Allows access only to authenticated users.
+    """
+
+    def has_permission(self, request, view):
+        return bool(request.user and request.user.is_authenticated)
+
+
+class IsAdminUser(BasePermission):
+    """
+    Allows access only to admin users.
+    """
+
+    def has_permission(self, request, view):
+        return bool(request.user and request.user.is_staff)
+
+
+class IsAuthenticatedOrReadOnly(BasePermission):
+    """
+    The request is authenticated as a user, or is a read-only request.
+    """
+
+    def has_permission(self, request, view):
+        return bool(
+            request.method in SAFE_METHODS or
+            request.user and
+            request.user.is_authenticated
+        )
+
+
+class DjangoModelPermissions(BasePermission):
+    """
+    The request is authenticated using `django.contrib.auth` permissions.
+    See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
+
+    It ensures that the user is authenticated, and has the appropriate
+    `add`/`change`/`delete` permissions on the model.
+
+    This permission can only be applied against view classes that
+    provide a `.queryset` attribute.
+    """
+
+    # Map methods into required permission codes.
+    # Override this if you need to also provide 'view' permissions,
+    # or if you want to provide custom permission codes.
+    perms_map = {
+        'GET': [],
+        'OPTIONS': [],
+        'HEAD': [],
+        'POST': ['%(app_label)s.add_%(model_name)s'],
+        'PUT': ['%(app_label)s.change_%(model_name)s'],
+        'PATCH': ['%(app_label)s.change_%(model_name)s'],
+        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
+    }
+
+    authenticated_users_only = True
+
+    def get_required_permissions(self, method, model_cls):
+        """
+        Given a model and an HTTP method, return the list of permission
+        codes that the user is required to have.
+        """
+        kwargs = {
+            'app_label': model_cls._meta.app_label,
+            'model_name': model_cls._meta.model_name
+        }
+
+        if method not in self.perms_map:
+            raise exceptions.MethodNotAllowed(method)
+
+        return [perm % kwargs for perm in self.perms_map[method]]
+
+    def _queryset(self, view):
+        assert hasattr(view, 'get_queryset') \
+            or getattr(view, 'queryset', None) is not None, (
+            'Cannot apply {} on a view that does not set '
+            '`.queryset` or have a `.get_queryset()` method.'
+        ).format(self.__class__.__name__)
+
+        if hasattr(view, 'get_queryset'):
+            queryset = view.get_queryset()
+            assert queryset is not None, (
+                '{}.get_queryset() returned None'.format(view.__class__.__name__)
+            )
+            return queryset
+        return view.queryset
+
+    def has_permission(self, request, view):
+        # Workaround to ensure DjangoModelPermissions are not applied
+        # to the root view when using DefaultRouter.
+        if getattr(view, '_ignore_model_permissions', False):
+            return True
+
+        if not request.user or (
+           not request.user.is_authenticated and self.authenticated_users_only):
+            return False
+
+        queryset = self._queryset(view)
+        perms = self.get_required_permissions(request.method, queryset.model)
+
+        return request.user.has_perms(perms)
+
+
+class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
+    """
+    Similar to DjangoModelPermissions, except that anonymous users are
+    allowed read-only access.
+    """
+    authenticated_users_only = False
+
+
+class DjangoObjectPermissions(DjangoModelPermissions):
+    """
+    The request is authenticated using Django's object-level permissions.
+    It requires an object-permissions-enabled backend, such as Django Guardian.
+
+    It ensures that the user is authenticated, and has the appropriate
+    `add`/`change`/`delete` permissions on the object using .has_perms.
+
+    This permission can only be applied against view classes that
+    provide a `.queryset` attribute.
+    """
+    perms_map = {
+        'GET': [],
+        'OPTIONS': [],
+        'HEAD': [],
+        'POST': ['%(app_label)s.add_%(model_name)s'],
+        'PUT': ['%(app_label)s.change_%(model_name)s'],
+        'PATCH': ['%(app_label)s.change_%(model_name)s'],
+        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
+    }
+
+    def get_required_object_permissions(self, method, model_cls):
+        kwargs = {
+            'app_label': model_cls._meta.app_label,
+            'model_name': model_cls._meta.model_name
+        }
+
+        if method not in self.perms_map:
+            raise exceptions.MethodNotAllowed(method)
+
+        return [perm % kwargs for perm in self.perms_map[method]]
+
+    def has_object_permission(self, request, view, obj):
+        # authentication checks have already executed via has_permission
+        queryset = self._queryset(view)
+        model_cls = queryset.model
+        user = request.user
+
+        perms = self.get_required_object_permissions(request.method, model_cls)
+
+        if not user.has_perms(perms, obj):
+            # If the user does not have permissions we need to determine if
+            # they have read permissions to see 403, or not, and simply see
+            # a 404 response.
+
+            if request.method in SAFE_METHODS:
+                # Read permissions already checked and failed, no need
+                # to make another lookup.
+                raise Http404
+
+            read_perms = self.get_required_object_permissions('GET', model_cls)
+            if not user.has_perms(read_perms, obj):
+                raise Http404
+
+            # Has read permissions.
+            return False
+
+        return True

+ 561 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/relations.py

@@ -0,0 +1,561 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import sys
+from collections import OrderedDict
+
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+from django.db.models import Manager
+from django.db.models.query import QuerySet
+from django.urls import NoReverseMatch, Resolver404, get_script_prefix, resolve
+from django.utils import six
+from django.utils.encoding import (
+    python_2_unicode_compatible, smart_text, uri_to_iri
+)
+from django.utils.six.moves.urllib import parse as urlparse
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework.fields import (
+    Field, empty, get_attribute, is_simple_callable, iter_options
+)
+from rest_framework.reverse import reverse
+from rest_framework.settings import api_settings
+from rest_framework.utils import html
+
+
+def method_overridden(method_name, klass, instance):
+    """
+    Determine if a method has been overridden.
+    """
+    method = getattr(klass, method_name)
+    default_method = getattr(method, '__func__', method)  # Python 3 compat
+    return default_method is not getattr(instance, method_name).__func__
+
+
+class ObjectValueError(ValueError):
+    """
+    Raised when `queryset.get()` failed due to an underlying `ValueError`.
+    Wrapping prevents calling code conflating this with unrelated errors.
+    """
+
+
+class ObjectTypeError(TypeError):
+    """
+    Raised when `queryset.get()` failed due to an underlying `TypeError`.
+    Wrapping prevents calling code conflating this with unrelated errors.
+    """
+
+
+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.
+    """
+    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
+
+
+@python_2_unicode_compatible
+class PKOnlyObject(object):
+    """
+    This is a mock object, used for when we only need the pk of the object
+    instance, but still want to return an object with a .pk attribute,
+    in order to keep the same interface as a regular model instance.
+    """
+    def __init__(self, pk):
+        self.pk = pk
+
+    def __str__(self):
+        return "%s" % self.pk
+
+
+# We assume that 'validators' are intended for the child serializer,
+# rather than the parent serializer.
+MANY_RELATION_KWARGS = (
+    'read_only', 'write_only', 'required', 'default', 'initial', 'source',
+    'label', 'help_text', 'style', 'error_messages', 'allow_empty',
+    'html_cutoff', 'html_cutoff_text'
+)
+
+
+class RelatedField(Field):
+    queryset = None
+    html_cutoff = None
+    html_cutoff_text = None
+
+    def __init__(self, **kwargs):
+        self.queryset = kwargs.pop('queryset', self.queryset)
+
+        cutoff_from_settings = api_settings.HTML_SELECT_CUTOFF
+        if cutoff_from_settings is not None:
+            cutoff_from_settings = int(cutoff_from_settings)
+        self.html_cutoff = kwargs.pop('html_cutoff', cutoff_from_settings)
+
+        self.html_cutoff_text = kwargs.pop(
+            'html_cutoff_text',
+            self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT)
+        )
+        if not method_overridden('get_queryset', RelatedField, self):
+            assert self.queryset is not None or kwargs.get('read_only', None), (
+                'Relational field must provide a `queryset` argument, '
+                'override `get_queryset`, or set read_only=`True`.'
+            )
+        assert not (self.queryset is not None and kwargs.get('read_only', None)), (
+            'Relational fields should not provide a `queryset` argument, '
+            'when setting read_only=`True`.'
+        )
+        kwargs.pop('many', None)
+        kwargs.pop('allow_empty', None)
+        super(RelatedField, self).__init__(**kwargs)
+
+    def __new__(cls, *args, **kwargs):
+        # We override this method in order to automagically create
+        # `ManyRelatedField` classes instead when `many=True` is set.
+        if kwargs.pop('many', False):
+            return cls.many_init(*args, **kwargs)
+        return super(RelatedField, cls).__new__(cls, *args, **kwargs)
+
+    @classmethod
+    def many_init(cls, *args, **kwargs):
+        """
+        This method handles creating a parent `ManyRelatedField` instance
+        when the `many=True` keyword argument is passed.
+
+        Typically you won't need to override this method.
+
+        Note that we're over-cautious in passing most arguments to both parent
+        and child classes in order to try to cover the general case. If you're
+        overriding this method you'll probably want something much simpler, eg:
+
+        @classmethod
+        def many_init(cls, *args, **kwargs):
+            kwargs['child'] = cls()
+            return CustomManyRelatedField(*args, **kwargs)
+        """
+        list_kwargs = {'child_relation': cls(*args, **kwargs)}
+        for key in kwargs:
+            if key in MANY_RELATION_KWARGS:
+                list_kwargs[key] = kwargs[key]
+        return ManyRelatedField(**list_kwargs)
+
+    def run_validation(self, data=empty):
+        # We force empty strings to None values for relational fields.
+        if data == '':
+            data = None
+        return super(RelatedField, self).run_validation(data)
+
+    def get_queryset(self):
+        queryset = self.queryset
+        if isinstance(queryset, (QuerySet, Manager)):
+            # Ensure queryset is re-evaluated whenever used.
+            # Note that actually a `Manager` class may also be used as the
+            # queryset argument. This occurs on ModelSerializer fields,
+            # as it allows us to generate a more expressive 'repr' output
+            # for the field.
+            # Eg: 'MyRelationship(queryset=ExampleModel.objects.all())'
+            queryset = queryset.all()
+        return queryset
+
+    def use_pk_only_optimization(self):
+        return False
+
+    def get_attribute(self, instance):
+        if self.use_pk_only_optimization() and self.source_attrs:
+            # Optimized case, return a mock object only containing the pk attribute.
+            try:
+                attribute_instance = get_attribute(instance, self.source_attrs[:-1])
+                value = attribute_instance.serializable_value(self.source_attrs[-1])
+                if is_simple_callable(value):
+                    # Handle edge case where the relationship `source` argument
+                    # points to a `get_relationship()` method on the model
+                    value = value().pk
+                return PKOnlyObject(pk=value)
+            except AttributeError:
+                pass
+
+        # Standard case, return the object instance.
+        return super(RelatedField, self).get_attribute(instance)
+
+    def get_choices(self, cutoff=None):
+        queryset = self.get_queryset()
+        if queryset is None:
+            # Ensure that field.choices returns something sensible
+            # even when accessed with a read-only field.
+            return {}
+
+        if cutoff is not None:
+            queryset = queryset[:cutoff]
+
+        return OrderedDict([
+            (
+                self.to_representation(item),
+                self.display_value(item)
+            )
+            for item in queryset
+        ])
+
+    @property
+    def choices(self):
+        return self.get_choices()
+
+    @property
+    def grouped_choices(self):
+        return self.choices
+
+    def iter_options(self):
+        return iter_options(
+            self.get_choices(cutoff=self.html_cutoff),
+            cutoff=self.html_cutoff,
+            cutoff_text=self.html_cutoff_text
+        )
+
+    def display_value(self, instance):
+        return six.text_type(instance)
+
+
+class StringRelatedField(RelatedField):
+    """
+    A read only field that represents its targets using their
+    plain string representation.
+    """
+
+    def __init__(self, **kwargs):
+        kwargs['read_only'] = True
+        super(StringRelatedField, self).__init__(**kwargs)
+
+    def to_representation(self, value):
+        return six.text_type(value)
+
+
+class PrimaryKeyRelatedField(RelatedField):
+    default_error_messages = {
+        'required': _('This field is required.'),
+        'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
+        'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
+    }
+
+    def __init__(self, **kwargs):
+        self.pk_field = kwargs.pop('pk_field', None)
+        super(PrimaryKeyRelatedField, self).__init__(**kwargs)
+
+    def use_pk_only_optimization(self):
+        return True
+
+    def to_internal_value(self, data):
+        if self.pk_field is not None:
+            data = self.pk_field.to_internal_value(data)
+        try:
+            return self.get_queryset().get(pk=data)
+        except ObjectDoesNotExist:
+            self.fail('does_not_exist', pk_value=data)
+        except (TypeError, ValueError):
+            self.fail('incorrect_type', data_type=type(data).__name__)
+
+    def to_representation(self, value):
+        if self.pk_field is not None:
+            return self.pk_field.to_representation(value.pk)
+        return value.pk
+
+
+class HyperlinkedRelatedField(RelatedField):
+    lookup_field = 'pk'
+    view_name = None
+
+    default_error_messages = {
+        'required': _('This field is required.'),
+        'no_match': _('Invalid hyperlink - No URL match.'),
+        'incorrect_match': _('Invalid hyperlink - Incorrect URL match.'),
+        'does_not_exist': _('Invalid hyperlink - Object does not exist.'),
+        'incorrect_type': _('Incorrect type. Expected URL string, received {data_type}.'),
+    }
+
+    def __init__(self, view_name=None, **kwargs):
+        if view_name is not None:
+            self.view_name = view_name
+        assert self.view_name is not None, 'The `view_name` argument is required.'
+        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
+        self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
+        self.format = kwargs.pop('format', None)
+
+        # We include this simply for dependency injection in tests.
+        # We can't add it as a class attributes or it would expect an
+        # implicit `self` argument to be passed.
+        self.reverse = reverse
+
+        super(HyperlinkedRelatedField, self).__init__(**kwargs)
+
+    def use_pk_only_optimization(self):
+        return self.lookup_field == 'pk'
+
+    def get_object(self, view_name, view_args, view_kwargs):
+        """
+        Return the object corresponding to a matched URL.
+
+        Takes the matched URL conf arguments, and should return an
+        object instance, or raise an `ObjectDoesNotExist` exception.
+        """
+        lookup_value = view_kwargs[self.lookup_url_kwarg]
+        lookup_kwargs = {self.lookup_field: lookup_value}
+        queryset = self.get_queryset()
+
+        try:
+            return queryset.get(**lookup_kwargs)
+        except ValueError:
+            exc = ObjectValueError(str(sys.exc_info()[1]))
+            six.reraise(type(exc), exc, sys.exc_info()[2])
+        except TypeError:
+            exc = ObjectTypeError(str(sys.exc_info()[1]))
+            six.reraise(type(exc), exc, sys.exc_info()[2])
+
+    def get_url(self, obj, view_name, request, format):
+        """
+        Given an object, return the URL that hyperlinks to the object.
+
+        May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
+        attributes are not configured to correctly match the URL conf.
+        """
+        # Unsaved objects will not yet have a valid URL.
+        if hasattr(obj, 'pk') and obj.pk in (None, ''):
+            return None
+
+        lookup_value = getattr(obj, self.lookup_field)
+        kwargs = {self.lookup_url_kwarg: lookup_value}
+        return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
+
+    def to_internal_value(self, data):
+        request = self.context.get('request', None)
+        try:
+            http_prefix = data.startswith(('http:', 'https:'))
+        except AttributeError:
+            self.fail('incorrect_type', data_type=type(data).__name__)
+
+        if http_prefix:
+            # If needed convert absolute URLs to relative path
+            data = urlparse.urlparse(data).path
+            prefix = get_script_prefix()
+            if data.startswith(prefix):
+                data = '/' + data[len(prefix):]
+
+        data = uri_to_iri(data)
+
+        try:
+            match = resolve(data)
+        except Resolver404:
+            self.fail('no_match')
+
+        try:
+            expected_viewname = request.versioning_scheme.get_versioned_viewname(
+                self.view_name, request
+            )
+        except AttributeError:
+            expected_viewname = self.view_name
+
+        if match.view_name != expected_viewname:
+            self.fail('incorrect_match')
+
+        try:
+            return self.get_object(match.view_name, match.args, match.kwargs)
+        except (ObjectDoesNotExist, ObjectValueError, ObjectTypeError):
+            self.fail('does_not_exist')
+
+    def to_representation(self, value):
+        assert 'request' in self.context, (
+            "`%s` requires the request in the serializer"
+            " context. Add `context={'request': request}` when instantiating "
+            "the serializer." % self.__class__.__name__
+        )
+
+        request = self.context['request']
+        format = self.context.get('format', None)
+
+        # By default use whatever format is given for the current context
+        # unless the target is a different type to the source.
+        #
+        # Eg. Consider a HyperlinkedIdentityField pointing from a json
+        # representation to an html property of that representation...
+        #
+        # '/snippets/1/' should link to '/snippets/1/highlight/'
+        # ...but...
+        # '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
+        if format and self.format and self.format != format:
+            format = self.format
+
+        # Return the hyperlink, or error if incorrectly configured.
+        try:
+            url = self.get_url(value, self.view_name, request, format)
+        except NoReverseMatch:
+            msg = (
+                'Could not resolve URL for hyperlinked relationship using '
+                'view name "%s". You may have failed to include the related '
+                'model in your API, or incorrectly configured the '
+                '`lookup_field` attribute on this field.'
+            )
+            if value in ('', None):
+                value_string = {'': 'the empty string', None: 'None'}[value]
+                msg += (
+                    " WARNING: The value of the field on the model instance "
+                    "was %s, which may be why it didn't match any "
+                    "entries in your URL conf." % value_string
+                )
+            raise ImproperlyConfigured(msg % self.view_name)
+
+        if url is None:
+            return None
+
+        return Hyperlink(url, value)
+
+
+class HyperlinkedIdentityField(HyperlinkedRelatedField):
+    """
+    A read-only field that represents the identity URL for an object, itself.
+
+    This is in contrast to `HyperlinkedRelatedField` which represents the
+    URL of relationships to other objects.
+    """
+
+    def __init__(self, view_name=None, **kwargs):
+        assert view_name is not None, 'The `view_name` argument is required.'
+        kwargs['read_only'] = True
+        kwargs['source'] = '*'
+        super(HyperlinkedIdentityField, self).__init__(view_name, **kwargs)
+
+    def use_pk_only_optimization(self):
+        # We have the complete object instance already. We don't need
+        # to run the 'only get the pk for this relationship' code.
+        return False
+
+
+class SlugRelatedField(RelatedField):
+    """
+    A read-write field that represents the target of the relationship
+    by a unique 'slug' attribute.
+    """
+    default_error_messages = {
+        'does_not_exist': _('Object with {slug_name}={value} does not exist.'),
+        'invalid': _('Invalid value.'),
+    }
+
+    def __init__(self, slug_field=None, **kwargs):
+        assert slug_field is not None, 'The `slug_field` argument is required.'
+        self.slug_field = slug_field
+        super(SlugRelatedField, self).__init__(**kwargs)
+
+    def to_internal_value(self, data):
+        try:
+            return self.get_queryset().get(**{self.slug_field: data})
+        except ObjectDoesNotExist:
+            self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
+        except (TypeError, ValueError):
+            self.fail('invalid')
+
+    def to_representation(self, obj):
+        return getattr(obj, self.slug_field)
+
+
+class ManyRelatedField(Field):
+    """
+    Relationships with `many=True` transparently get coerced into instead being
+    a ManyRelatedField with a child relationship.
+
+    The `ManyRelatedField` class is responsible for handling iterating through
+    the values and passing each one to the child relationship.
+
+    This class is treated as private API.
+    You shouldn't generally need to be using this class directly yourself,
+    and should instead simply set 'many=True' on the relationship.
+    """
+    initial = []
+    default_empty_html = []
+    default_error_messages = {
+        'not_a_list': _('Expected a list of items but got type "{input_type}".'),
+        'empty': _('This list may not be empty.')
+    }
+    html_cutoff = None
+    html_cutoff_text = None
+
+    def __init__(self, child_relation=None, *args, **kwargs):
+        self.child_relation = child_relation
+        self.allow_empty = kwargs.pop('allow_empty', True)
+
+        cutoff_from_settings = api_settings.HTML_SELECT_CUTOFF
+        if cutoff_from_settings is not None:
+            cutoff_from_settings = int(cutoff_from_settings)
+        self.html_cutoff = kwargs.pop('html_cutoff', cutoff_from_settings)
+
+        self.html_cutoff_text = kwargs.pop(
+            'html_cutoff_text',
+            self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT)
+        )
+        assert child_relation is not None, '`child_relation` is a required argument.'
+        super(ManyRelatedField, self).__init__(*args, **kwargs)
+        self.child_relation.bind(field_name='', parent=self)
+
+    def get_value(self, dictionary):
+        # We override the default field access in order to support
+        # lists in HTML forms.
+        if html.is_html_input(dictionary):
+            # Don't return [] if the update is partial
+            if self.field_name not in dictionary:
+                if getattr(self.root, 'partial', False):
+                    return empty
+            return dictionary.getlist(self.field_name)
+
+        return dictionary.get(self.field_name, empty)
+
+    def to_internal_value(self, data):
+        if isinstance(data, six.text_type) or not hasattr(data, '__iter__'):
+            self.fail('not_a_list', input_type=type(data).__name__)
+        if not self.allow_empty and len(data) == 0:
+            self.fail('empty')
+
+        return [
+            self.child_relation.to_internal_value(item)
+            for item in data
+        ]
+
+    def get_attribute(self, instance):
+        # Can't have any relationships if not created
+        if hasattr(instance, 'pk') and instance.pk is None:
+            return []
+
+        relationship = get_attribute(instance, self.source_attrs)
+        return relationship.all() if hasattr(relationship, 'all') else relationship
+
+    def to_representation(self, iterable):
+        return [
+            self.child_relation.to_representation(value)
+            for value in iterable
+        ]
+
+    def get_choices(self, cutoff=None):
+        return self.child_relation.get_choices(cutoff)
+
+    @property
+    def choices(self):
+        return self.get_choices()
+
+    @property
+    def grouped_choices(self):
+        return self.choices
+
+    def iter_options(self):
+        return iter_options(
+            self.get_choices(cutoff=self.html_cutoff),
+            cutoff=self.html_cutoff,
+            cutoff_text=self.html_cutoff_text
+        )

+ 1051 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/renderers.py

@@ -0,0 +1,1051 @@
+"""
+Renderers are used to serialize a response into specific media types.
+
+They give us a generic way of being able to handle various media types
+on the response, such as JSON encoded data or HTML output.
+
+REST framework also provides an HTML renderer that renders the browsable API.
+"""
+from __future__ import unicode_literals
+
+import base64
+from collections import OrderedDict
+
+from django import forms
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.paginator import Page
+from django.http.multipartparser import parse_header
+from django.template import engines, loader
+from django.test.client import encode_multipart
+from django.urls import NoReverseMatch
+from django.utils import six
+from django.utils.html import mark_safe
+from django.utils.six.moves.urllib import parse as urlparse
+
+from rest_framework import VERSION, exceptions, serializers, status
+from rest_framework.compat import (
+    INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
+    pygments_css, yaml
+)
+from rest_framework.exceptions import ParseError
+from rest_framework.request import is_form_media_type, override_method
+from rest_framework.settings import api_settings
+from rest_framework.utils import encoders, json
+from rest_framework.utils.breadcrumbs import get_breadcrumbs
+from rest_framework.utils.field_mapping import ClassLookupDict
+
+
+def zero_as_none(value):
+    return None if value == 0 else value
+
+
+class BaseRenderer(object):
+    """
+    All renderers should extend this class, setting the `media_type`
+    and `format` attributes, and override the `.render()` method.
+    """
+    media_type = None
+    format = None
+    charset = 'utf-8'
+    render_style = 'text'
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        raise NotImplementedError('Renderer class requires .render() to be implemented')
+
+
+class JSONRenderer(BaseRenderer):
+    """
+    Renderer which serializes to JSON.
+    """
+    media_type = 'application/json'
+    format = 'json'
+    encoder_class = encoders.JSONEncoder
+    ensure_ascii = not api_settings.UNICODE_JSON
+    compact = api_settings.COMPACT_JSON
+    strict = api_settings.STRICT_JSON
+
+    # We don't set a charset because JSON is a binary encoding,
+    # that can be encoded as utf-8, utf-16 or utf-32.
+    # See: https://www.ietf.org/rfc/rfc4627.txt
+    # Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/
+    charset = None
+
+    def get_indent(self, accepted_media_type, renderer_context):
+        if accepted_media_type:
+            # If the media type looks like 'application/json; indent=4',
+            # then pretty print the result.
+            # Note that we coerce `indent=0` into `indent=None`.
+            base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
+            try:
+                return zero_as_none(max(min(int(params['indent']), 8), 0))
+            except (KeyError, ValueError, TypeError):
+                pass
+
+        # If 'indent' is provided in the context, then pretty print the result.
+        # E.g. If we're being called by the BrowsableAPIRenderer.
+        return renderer_context.get('indent', None)
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        """
+        Render `data` into JSON, returning a bytestring.
+        """
+        if data is None:
+            return bytes()
+
+        renderer_context = renderer_context or {}
+        indent = self.get_indent(accepted_media_type, renderer_context)
+
+        if indent is None:
+            separators = SHORT_SEPARATORS if self.compact else LONG_SEPARATORS
+        else:
+            separators = INDENT_SEPARATORS
+
+        ret = json.dumps(
+            data, cls=self.encoder_class,
+            indent=indent, ensure_ascii=self.ensure_ascii,
+            allow_nan=not self.strict, separators=separators
+        )
+
+        # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
+        # but if ensure_ascii=False, the return type is underspecified,
+        # and may (or may not) be unicode.
+        # On python 3.x json.dumps() returns unicode strings.
+        if isinstance(ret, six.text_type):
+            # We always fully escape \u2028 and \u2029 to ensure we output JSON
+            # that is a strict javascript subset. If bytes were returned
+            # by json.dumps() then we don't have these characters in any case.
+            # See: http://timelessrepo.com/json-isnt-a-javascript-subset
+            ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029')
+            return bytes(ret.encode('utf-8'))
+        return ret
+
+
+class TemplateHTMLRenderer(BaseRenderer):
+    """
+    An HTML renderer for use with templates.
+
+    The data supplied to the Response object should be a dictionary that will
+    be used as context for the template.
+
+    The template name is determined by (in order of preference):
+
+    1. An explicit `.template_name` attribute set on the response.
+    2. An explicit `.template_name` attribute set on this class.
+    3. The return result of calling `view.get_template_names()`.
+
+    For example:
+        data = {'users': User.objects.all()}
+        return Response(data, template_name='users.html')
+
+    For pre-rendered HTML, see StaticHTMLRenderer.
+    """
+    media_type = 'text/html'
+    format = 'html'
+    template_name = None
+    exception_template_names = [
+        '%(status_code)s.html',
+        'api_exception.html'
+    ]
+    charset = 'utf-8'
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        """
+        Renders data to HTML, using Django's standard template rendering.
+
+        The template name is determined by (in order of preference):
+
+        1. An explicit .template_name set on the response.
+        2. An explicit .template_name set on this class.
+        3. The return result of calling view.get_template_names().
+        """
+        renderer_context = renderer_context or {}
+        view = renderer_context['view']
+        request = renderer_context['request']
+        response = renderer_context['response']
+
+        if response.exception:
+            template = self.get_exception_template(response)
+        else:
+            template_names = self.get_template_names(response, view)
+            template = self.resolve_template(template_names)
+
+        if hasattr(self, 'resolve_context'):
+            # Fallback for older versions.
+            context = self.resolve_context(data, request, response)
+        else:
+            context = self.get_template_context(data, renderer_context)
+        return template.render(context, request=request)
+
+    def resolve_template(self, template_names):
+        return loader.select_template(template_names)
+
+    def get_template_context(self, data, renderer_context):
+        response = renderer_context['response']
+        if response.exception:
+            data['status_code'] = response.status_code
+        return data
+
+    def get_template_names(self, response, view):
+        if response.template_name:
+            return [response.template_name]
+        elif self.template_name:
+            return [self.template_name]
+        elif hasattr(view, 'get_template_names'):
+            return view.get_template_names()
+        elif hasattr(view, 'template_name'):
+            return [view.template_name]
+        raise ImproperlyConfigured(
+            'Returned a template response with no `template_name` attribute set on either the view or response'
+        )
+
+    def get_exception_template(self, response):
+        template_names = [name % {'status_code': response.status_code}
+                          for name in self.exception_template_names]
+
+        try:
+            # Try to find an appropriate error template
+            return self.resolve_template(template_names)
+        except Exception:
+            # Fall back to using eg '404 Not Found'
+            body = '%d %s' % (response.status_code, response.status_text.title())
+            template = engines['django'].from_string(body)
+            return template
+
+
+# Note, subclass TemplateHTMLRenderer simply for the exception behavior
+class StaticHTMLRenderer(TemplateHTMLRenderer):
+    """
+    An HTML renderer class that simply returns pre-rendered HTML.
+
+    The data supplied to the Response object should be a string representing
+    the pre-rendered HTML content.
+
+    For example:
+        data = '<html><body>example</body></html>'
+        return Response(data)
+
+    For template rendered HTML, see TemplateHTMLRenderer.
+    """
+    media_type = 'text/html'
+    format = 'html'
+    charset = 'utf-8'
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        renderer_context = renderer_context or {}
+        response = renderer_context.get('response')
+
+        if response and response.exception:
+            request = renderer_context['request']
+            template = self.get_exception_template(response)
+            if hasattr(self, 'resolve_context'):
+                context = self.resolve_context(data, request, response)
+            else:
+                context = self.get_template_context(data, renderer_context)
+            return template.render(context, request=request)
+
+        return data
+
+
+class HTMLFormRenderer(BaseRenderer):
+    """
+    Renderers serializer data into an HTML form.
+
+    If the serializer was instantiated without an object then this will
+    return an HTML form not bound to any object,
+    otherwise it will return an HTML form with the appropriate initial data
+    populated from the object.
+
+    Note that rendering of field and form errors is not currently supported.
+    """
+    media_type = 'text/html'
+    format = 'form'
+    charset = 'utf-8'
+    template_pack = 'rest_framework/vertical/'
+    base_template = 'form.html'
+
+    default_style = ClassLookupDict({
+        serializers.Field: {
+            'base_template': 'input.html',
+            'input_type': 'text'
+        },
+        serializers.EmailField: {
+            'base_template': 'input.html',
+            'input_type': 'email'
+        },
+        serializers.URLField: {
+            'base_template': 'input.html',
+            'input_type': 'url'
+        },
+        serializers.IntegerField: {
+            'base_template': 'input.html',
+            'input_type': 'number'
+        },
+        serializers.FloatField: {
+            'base_template': 'input.html',
+            'input_type': 'number'
+        },
+        serializers.DateTimeField: {
+            'base_template': 'input.html',
+            'input_type': 'datetime-local'
+        },
+        serializers.DateField: {
+            'base_template': 'input.html',
+            'input_type': 'date'
+        },
+        serializers.TimeField: {
+            'base_template': 'input.html',
+            'input_type': 'time'
+        },
+        serializers.FileField: {
+            'base_template': 'input.html',
+            'input_type': 'file'
+        },
+        serializers.BooleanField: {
+            'base_template': 'checkbox.html'
+        },
+        serializers.ChoiceField: {
+            'base_template': 'select.html',  # Also valid: 'radio.html'
+        },
+        serializers.MultipleChoiceField: {
+            'base_template': 'select_multiple.html',  # Also valid: 'checkbox_multiple.html'
+        },
+        serializers.RelatedField: {
+            'base_template': 'select.html',  # Also valid: 'radio.html'
+        },
+        serializers.ManyRelatedField: {
+            'base_template': 'select_multiple.html',  # Also valid: 'checkbox_multiple.html'
+        },
+        serializers.Serializer: {
+            'base_template': 'fieldset.html'
+        },
+        serializers.ListSerializer: {
+            'base_template': 'list_fieldset.html'
+        },
+        serializers.ListField: {
+            'base_template': 'list_field.html'
+        },
+        serializers.DictField: {
+            'base_template': 'dict_field.html'
+        },
+        serializers.FilePathField: {
+            'base_template': 'select.html',
+        },
+        serializers.JSONField: {
+            'base_template': 'textarea.html',
+        },
+    })
+
+    def render_field(self, field, parent_style):
+        if isinstance(field._field, serializers.HiddenField):
+            return ''
+
+        style = dict(self.default_style[field])
+        style.update(field.style)
+        if 'template_pack' not in style:
+            style['template_pack'] = parent_style.get('template_pack', self.template_pack)
+        style['renderer'] = self
+
+        # Get a clone of the field with text-only value representation.
+        field = field.as_form_field()
+
+        if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type):
+            field.value = field.value.rstrip('Z')
+
+        if 'template' in style:
+            template_name = style['template']
+        else:
+            template_name = style['template_pack'].strip('/') + '/' + style['base_template']
+
+        template = loader.get_template(template_name)
+        context = {'field': field, 'style': style}
+        return template.render(context)
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        """
+        Render serializer data and return an HTML form, as a string.
+        """
+        renderer_context = renderer_context or {}
+        form = data.serializer
+
+        style = renderer_context.get('style', {})
+        if 'template_pack' not in style:
+            style['template_pack'] = self.template_pack
+        style['renderer'] = self
+
+        template_pack = style['template_pack'].strip('/')
+        template_name = template_pack + '/' + self.base_template
+        template = loader.get_template(template_name)
+        context = {
+            'form': form,
+            'style': style
+        }
+        return template.render(context)
+
+
+class BrowsableAPIRenderer(BaseRenderer):
+    """
+    HTML renderer used to self-document the API.
+    """
+    media_type = 'text/html'
+    format = 'api'
+    template = 'rest_framework/api.html'
+    filter_template = 'rest_framework/filters/base.html'
+    code_style = 'emacs'
+    charset = 'utf-8'
+    form_renderer_class = HTMLFormRenderer
+
+    def get_default_renderer(self, view):
+        """
+        Return an instance of the first valid renderer.
+        (Don't use another documenting renderer.)
+        """
+        renderers = [renderer for renderer in view.renderer_classes
+                     if not issubclass(renderer, BrowsableAPIRenderer)]
+        non_template_renderers = [renderer for renderer in renderers
+                                  if not hasattr(renderer, 'get_template_names')]
+
+        if not renderers:
+            return None
+        elif non_template_renderers:
+            return non_template_renderers[0]()
+        return renderers[0]()
+
+    def get_content(self, renderer, data,
+                    accepted_media_type, renderer_context):
+        """
+        Get the content as if it had been rendered by the default
+        non-documenting renderer.
+        """
+        if not renderer:
+            return '[No renderers were found]'
+
+        renderer_context['indent'] = 4
+        content = renderer.render(data, accepted_media_type, renderer_context)
+
+        render_style = getattr(renderer, 'render_style', 'text')
+        assert render_style in ['text', 'binary'], 'Expected .render_style ' \
+            '"text" or "binary", but got "%s"' % render_style
+        if render_style == 'binary':
+            return '[%d bytes of binary content]' % len(content)
+
+        return content
+
+    def show_form_for_method(self, view, method, request, obj):
+        """
+        Returns True if a form should be shown for this method.
+        """
+        if method not in view.allowed_methods:
+            return  # Not a valid method
+
+        try:
+            view.check_permissions(request)
+            if obj is not None:
+                view.check_object_permissions(request, obj)
+        except exceptions.APIException:
+            return False  # Doesn't have permissions
+        return True
+
+    def _get_serializer(self, serializer_class, view_instance, request, *args, **kwargs):
+        kwargs['context'] = {
+            'request': request,
+            'format': self.format,
+            'view': view_instance
+        }
+        return serializer_class(*args, **kwargs)
+
+    def get_rendered_html_form(self, data, view, method, request):
+        """
+        Return a string representing a rendered HTML form, possibly bound to
+        either the input or output data.
+
+        In the absence of the View having an associated form then return None.
+        """
+        # See issue #2089 for refactoring this.
+        serializer = getattr(data, 'serializer', None)
+        if serializer and not getattr(serializer, 'many', False):
+            instance = getattr(serializer, 'instance', None)
+            if isinstance(instance, Page):
+                instance = None
+        else:
+            instance = None
+
+        # If this is valid serializer data, and the form is for the same
+        # HTTP method as was used in the request then use the existing
+        # serializer instance, rather than dynamically creating a new one.
+        if request.method == method and serializer is not None:
+            try:
+                kwargs = {'data': request.data}
+            except ParseError:
+                kwargs = {}
+            existing_serializer = serializer
+        else:
+            kwargs = {}
+            existing_serializer = None
+
+        with override_method(view, request, method) as request:
+            if not self.show_form_for_method(view, method, request, instance):
+                return
+
+            if method in ('DELETE', 'OPTIONS'):
+                return True  # Don't actually need to return a form
+
+            has_serializer = getattr(view, 'get_serializer', None)
+            has_serializer_class = getattr(view, 'serializer_class', None)
+
+            if (
+                (not has_serializer and not has_serializer_class) or
+                not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)
+            ):
+                return
+
+            if existing_serializer is not None:
+                try:
+                    return self.render_form_for_serializer(existing_serializer)
+                except TypeError:
+                    pass
+
+            if has_serializer:
+                if method in ('PUT', 'PATCH'):
+                    serializer = view.get_serializer(instance=instance, **kwargs)
+                else:
+                    serializer = view.get_serializer(**kwargs)
+            else:
+                # at this point we must have a serializer_class
+                if method in ('PUT', 'PATCH'):
+                    serializer = self._get_serializer(view.serializer_class, view,
+                                                      request, instance=instance, **kwargs)
+                else:
+                    serializer = self._get_serializer(view.serializer_class, view,
+                                                      request, **kwargs)
+
+            return self.render_form_for_serializer(serializer)
+
+    def render_form_for_serializer(self, serializer):
+        if hasattr(serializer, 'initial_data'):
+            serializer.is_valid()
+
+        form_renderer = self.form_renderer_class()
+        return form_renderer.render(
+            serializer.data,
+            self.accepted_media_type,
+            {'style': {'template_pack': 'rest_framework/horizontal'}}
+        )
+
+    def get_raw_data_form(self, data, view, method, request):
+        """
+        Returns a form that allows for arbitrary content types to be tunneled
+        via standard HTML forms.
+        (Which are typically application/x-www-form-urlencoded)
+        """
+        # See issue #2089 for refactoring this.
+        serializer = getattr(data, 'serializer', None)
+        if serializer and not getattr(serializer, 'many', False):
+            instance = getattr(serializer, 'instance', None)
+            if isinstance(instance, Page):
+                instance = None
+        else:
+            instance = None
+
+        with override_method(view, request, method) as request:
+            # Check permissions
+            if not self.show_form_for_method(view, method, request, instance):
+                return
+
+            # If possible, serialize the initial content for the generic form
+            default_parser = view.parser_classes[0]
+            renderer_class = getattr(default_parser, 'renderer_class', None)
+            if hasattr(view, 'get_serializer') and renderer_class:
+                # View has a serializer defined and parser class has a
+                # corresponding renderer that can be used to render the data.
+
+                if method in ('PUT', 'PATCH'):
+                    serializer = view.get_serializer(instance=instance)
+                else:
+                    serializer = view.get_serializer()
+
+                # Render the raw data content
+                renderer = renderer_class()
+                accepted = self.accepted_media_type
+                context = self.renderer_context.copy()
+                context['indent'] = 4
+
+                # strip HiddenField from output
+                data = serializer.data.copy()
+                for name, field in serializer.fields.items():
+                    if isinstance(field, serializers.HiddenField):
+                        data.pop(name, None)
+                content = renderer.render(data, accepted, context)
+                # Renders returns bytes, but CharField expects a str.
+                content = content.decode('utf-8')
+            else:
+                content = None
+
+            # Generate a generic form that includes a content type field,
+            # and a content field.
+            media_types = [parser.media_type for parser in view.parser_classes]
+            choices = [(media_type, media_type) for media_type in media_types]
+            initial = media_types[0]
+
+            class GenericContentForm(forms.Form):
+                _content_type = forms.ChoiceField(
+                    label='Media type',
+                    choices=choices,
+                    initial=initial,
+                    widget=forms.Select(attrs={'data-override': 'content-type'})
+                )
+                _content = forms.CharField(
+                    label='Content',
+                    widget=forms.Textarea(attrs={'data-override': 'content'}),
+                    initial=content,
+                    required=False
+                )
+
+            return GenericContentForm()
+
+    def get_name(self, view):
+        return view.get_view_name()
+
+    def get_description(self, view, status_code):
+        if status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN):
+            return ''
+        return view.get_view_description(html=True)
+
+    def get_breadcrumbs(self, request):
+        return get_breadcrumbs(request.path, request)
+
+    def get_extra_actions(self, view):
+        if hasattr(view, 'get_extra_action_url_map'):
+            return view.get_extra_action_url_map()
+        return None
+
+    def get_filter_form(self, data, view, request):
+        if not hasattr(view, 'get_queryset') or not hasattr(view, 'filter_backends'):
+            return
+
+        # Infer if this is a list view or not.
+        paginator = getattr(view, 'paginator', None)
+        if isinstance(data, list):
+            pass
+        elif paginator is not None and data is not None:
+            try:
+                paginator.get_results(data)
+            except (TypeError, KeyError):
+                return
+        elif not isinstance(data, list):
+            return
+
+        queryset = view.get_queryset()
+        elements = []
+        for backend in view.filter_backends:
+            if hasattr(backend, 'to_html'):
+                html = backend().to_html(request, queryset, view)
+                if html:
+                    elements.append(html)
+
+        if not elements:
+            return
+
+        template = loader.get_template(self.filter_template)
+        context = {'elements': elements}
+        return template.render(context)
+
+    def get_context(self, data, accepted_media_type, renderer_context):
+        """
+        Returns the context used to render.
+        """
+        view = renderer_context['view']
+        request = renderer_context['request']
+        response = renderer_context['response']
+
+        renderer = self.get_default_renderer(view)
+
+        raw_data_post_form = self.get_raw_data_form(data, view, 'POST', request)
+        raw_data_put_form = self.get_raw_data_form(data, view, 'PUT', request)
+        raw_data_patch_form = self.get_raw_data_form(data, view, 'PATCH', request)
+        raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
+
+        response_headers = OrderedDict(sorted(response.items()))
+        renderer_content_type = ''
+        if renderer:
+            renderer_content_type = '%s' % renderer.media_type
+            if renderer.charset:
+                renderer_content_type += ' ;%s' % renderer.charset
+        response_headers['Content-Type'] = renderer_content_type
+
+        if getattr(view, 'paginator', None) and view.paginator.display_page_controls:
+            paginator = view.paginator
+        else:
+            paginator = None
+
+        csrf_cookie_name = settings.CSRF_COOKIE_NAME
+        csrf_header_name = settings.CSRF_HEADER_NAME
+        if csrf_header_name.startswith('HTTP_'):
+            csrf_header_name = csrf_header_name[5:]
+        csrf_header_name = csrf_header_name.replace('_', '-')
+
+        context = {
+            'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
+            'code_style': pygments_css(self.code_style),
+            'view': view,
+            'request': request,
+            'response': response,
+            'user': request.user,
+            'description': self.get_description(view, response.status_code),
+            'name': self.get_name(view),
+            'version': VERSION,
+            'paginator': paginator,
+            'breadcrumblist': self.get_breadcrumbs(request),
+            'allowed_methods': view.allowed_methods,
+            'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
+            'response_headers': response_headers,
+
+            'put_form': self.get_rendered_html_form(data, view, 'PUT', request),
+            'post_form': self.get_rendered_html_form(data, view, 'POST', request),
+            'delete_form': self.get_rendered_html_form(data, view, 'DELETE', request),
+            'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request),
+
+            'extra_actions': self.get_extra_actions(view),
+
+            'filter_form': self.get_filter_form(data, view, request),
+
+            'raw_data_put_form': raw_data_put_form,
+            'raw_data_post_form': raw_data_post_form,
+            'raw_data_patch_form': raw_data_patch_form,
+            'raw_data_put_or_patch_form': raw_data_put_or_patch_form,
+
+            'display_edit_forms': bool(response.status_code != 403),
+
+            'api_settings': api_settings,
+            'csrf_cookie_name': csrf_cookie_name,
+            'csrf_header_name': csrf_header_name
+        }
+        return context
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        """
+        Render the HTML for the browsable API representation.
+        """
+        self.accepted_media_type = accepted_media_type or ''
+        self.renderer_context = renderer_context or {}
+
+        template = loader.get_template(self.template)
+        context = self.get_context(data, accepted_media_type, renderer_context)
+        ret = template.render(context, request=renderer_context['request'])
+
+        # Munge DELETE Response code to allow us to return content
+        # (Do this *after* we've rendered the template so that we include
+        # the normal deletion response code in the output)
+        response = renderer_context['response']
+        if response.status_code == status.HTTP_204_NO_CONTENT:
+            response.status_code = status.HTTP_200_OK
+
+        return ret
+
+
+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 == status.HTTP_400_BAD_REQUEST:
+            # 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)
+        ret = template.render(context, request=renderer_context['request'])
+
+        # Creation and deletion should use redirects in the admin style.
+        if response.status_code == status.HTTP_201_CREATED and 'Location' in response:
+            response.status_code = status.HTTP_303_SEE_OTHER
+            response['Location'] = request.build_absolute_uri()
+            ret = ''
+
+        if response.status_code == status.HTTP_204_NO_CONTENT:
+            response.status_code = status.HTTP_303_SEE_OTHER
+            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 (TypeError, 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 if key != 'url']
+        details = [key for key in header if key != 'url']
+
+        if isinstance(results, list) and 'view' in renderer_context:
+            for result in results:
+                url = self.get_result_url(result, context['view'])
+                if url is not None:
+                    result.setdefault('url', 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 get_result_url(self, result, view):
+        """
+        Attempt to reverse the result's detail view URL.
+
+        This only works with views that are generic-like (has `.lookup_field`)
+        and viewset-like (has `.basename` / `.reverse_action()`).
+        """
+        if not hasattr(view, 'reverse_action') or \
+           not hasattr(view, 'lookup_field'):
+            return
+
+        lookup_field = view.lookup_field
+        lookup_url_kwarg = getattr(view, 'lookup_url_kwarg', None) or lookup_field
+
+        try:
+            kwargs = {lookup_url_kwarg: result[lookup_field]}
+            return view.reverse_action('detail', kwargs=kwargs)
+        except (KeyError, NoReverseMatch):
+            return
+
+
+class DocumentationRenderer(BaseRenderer):
+    media_type = 'text/html'
+    format = 'html'
+    charset = 'utf-8'
+    template = 'rest_framework/docs/index.html'
+    error_template = 'rest_framework/docs/error.html'
+    code_style = 'emacs'
+    languages = ['shell', 'javascript', 'python']
+
+    def get_context(self, data, request):
+        return {
+            'document': data,
+            'langs': self.languages,
+            'lang_htmls': ["rest_framework/docs/langs/%s.html" % l for l in self.languages],
+            'lang_intro_htmls': ["rest_framework/docs/langs/%s-intro.html" % l for l in self.languages],
+            'code_style': pygments_css(self.code_style),
+            'request': request
+        }
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        if isinstance(data, coreapi.Document):
+            template = loader.get_template(self.template)
+            context = self.get_context(data, renderer_context['request'])
+            return template.render(context, request=renderer_context['request'])
+        else:
+            template = loader.get_template(self.error_template)
+            context = {
+                "data": data,
+                "request": renderer_context['request'],
+                "response": renderer_context['response'],
+                "debug": settings.DEBUG,
+            }
+            return template.render(context, request=renderer_context['request'])
+
+
+class SchemaJSRenderer(BaseRenderer):
+    media_type = 'application/javascript'
+    format = 'javascript'
+    charset = 'utf-8'
+    template = 'rest_framework/schema.js'
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        codec = coreapi.codecs.CoreJSONCodec()
+        schema = base64.b64encode(codec.encode(data)).decode('ascii')
+
+        template = loader.get_template(self.template)
+        context = {'schema': mark_safe(schema)}
+        request = renderer_context['request']
+        return template.render(context, request=request)
+
+
+class MultiPartRenderer(BaseRenderer):
+    media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
+    format = 'multipart'
+    charset = 'utf-8'
+    BOUNDARY = 'BoUnDaRyStRiNg'
+
+    def render(self, data, accepted_media_type=None, renderer_context=None):
+        if hasattr(data, 'items'):
+            for key, value in data.items():
+                assert not isinstance(value, dict), (
+                    "Test data contained a dictionary value for key '%s', "
+                    "but multipart uploads do not support nested data. "
+                    "You may want to consider using format='json' in this "
+                    "test case." % key
+                )
+        return encode_multipart(self.BOUNDARY, data)
+
+
+class CoreJSONRenderer(BaseRenderer):
+    media_type = 'application/coreapi+json'
+    charset = None
+    format = 'corejson'
+
+    def __init__(self):
+        assert coreapi, 'Using CoreJSONRenderer, but `coreapi` is not installed.'
+
+    def render(self, data, media_type=None, renderer_context=None):
+        indent = bool(renderer_context.get('indent', 0))
+        codec = coreapi.codecs.CoreJSONCodec()
+        return codec.dump(data, indent=indent)
+
+
+class _BaseOpenAPIRenderer:
+    def get_schema(self, instance):
+        CLASS_TO_TYPENAME = {
+            coreschema.Object: 'object',
+            coreschema.Array: 'array',
+            coreschema.Number: 'number',
+            coreschema.Integer: 'integer',
+            coreschema.String: 'string',
+            coreschema.Boolean: 'boolean',
+        }
+
+        schema = {}
+        if instance.__class__ in CLASS_TO_TYPENAME:
+            schema['type'] = CLASS_TO_TYPENAME[instance.__class__]
+        schema['title'] = instance.title
+        schema['description'] = instance.description
+        if hasattr(instance, 'enum'):
+            schema['enum'] = instance.enum
+        return schema
+
+    def get_parameters(self, link):
+        parameters = []
+        for field in link.fields:
+            if field.location not in ['path', 'query']:
+                continue
+            parameter = {
+                'name': field.name,
+                'in': field.location,
+            }
+            if field.required:
+                parameter['required'] = True
+            if field.description:
+                parameter['description'] = field.description
+            if field.schema:
+                parameter['schema'] = self.get_schema(field.schema)
+            parameters.append(parameter)
+        return parameters
+
+    def get_operation(self, link, name, tag):
+        operation_id = "%s_%s" % (tag, name) if tag else name
+        parameters = self.get_parameters(link)
+
+        operation = {
+            'operationId': operation_id,
+        }
+        if link.title:
+            operation['summary'] = link.title
+        if link.description:
+            operation['description'] = link.description
+        if parameters:
+            operation['parameters'] = parameters
+        if tag:
+            operation['tags'] = [tag]
+        return operation
+
+    def get_paths(self, document):
+        paths = {}
+
+        tag = None
+        for name, link in document.links.items():
+            path = urlparse.urlparse(link.url).path
+            method = link.action.lower()
+            paths.setdefault(path, {})
+            paths[path][method] = self.get_operation(link, name, tag=tag)
+
+        for tag, section in document.data.items():
+            for name, link in section.links.items():
+                path = urlparse.urlparse(link.url).path
+                method = link.action.lower()
+                paths.setdefault(path, {})
+                paths[path][method] = self.get_operation(link, name, tag=tag)
+
+        return paths
+
+    def get_structure(self, data):
+        return {
+            'openapi': '3.0.0',
+            'info': {
+                'version': '',
+                'title': data.title,
+                'description': data.description
+            },
+            'servers': [{
+                'url': data.url
+            }],
+            'paths': self.get_paths(data)
+        }
+
+
+class OpenAPIRenderer(_BaseOpenAPIRenderer):
+    media_type = 'application/vnd.oai.openapi'
+    charset = None
+    format = 'openapi'
+
+    def __init__(self):
+        assert coreapi, 'Using OpenAPIRenderer, but `coreapi` is not installed.'
+        assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.'
+
+    def render(self, data, media_type=None, renderer_context=None):
+        structure = self.get_structure(data)
+        return yaml.dump(structure, default_flow_style=False).encode('utf-8')
+
+
+class JSONOpenAPIRenderer(_BaseOpenAPIRenderer):
+    media_type = 'application/vnd.oai.openapi+json'
+    charset = None
+    format = 'openapi-json'
+
+    def __init__(self):
+        assert coreapi, 'Using JSONOpenAPIRenderer, but `coreapi` is not installed.'
+
+    def render(self, data, media_type=None, renderer_context=None):
+        structure = self.get_structure(data)
+        return json.dumps(structure, indent=4).encode('utf-8')

+ 451 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/request.py

@@ -0,0 +1,451 @@
+"""
+The Request class is used as a wrapper around the standard request object.
+
+The wrapped request then offers a richer API, in particular :
+
+    - content automatically parsed according to `Content-Type` header,
+      and available as `request.data`
+    - full support of PUT method, including support for file uploads
+    - form overloading of HTTP method, content type and content
+"""
+from __future__ import unicode_literals
+
+import io
+import sys
+from contextlib import contextmanager
+
+from django.conf import settings
+from django.http import HttpRequest, QueryDict
+from django.http.multipartparser import parse_header
+from django.http.request import RawPostDataException
+from django.utils import six
+from django.utils.datastructures import MultiValueDict
+
+from rest_framework import HTTP_HEADER_ENCODING, exceptions
+from rest_framework.settings import api_settings
+
+
+def is_form_media_type(media_type):
+    """
+    Return True if the media type is a valid form media type.
+    """
+    base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING))
+    return (base_media_type == 'application/x-www-form-urlencoded' or
+            base_media_type == 'multipart/form-data')
+
+
+class override_method(object):
+    """
+    A context manager that temporarily overrides the method on a request,
+    additionally setting the `view.request` attribute.
+
+    Usage:
+
+        with override_method(view, request, 'POST') as request:
+            ... # Do stuff with `view` and `request`
+    """
+
+    def __init__(self, view, request, method):
+        self.view = view
+        self.request = request
+        self.method = method
+        self.action = getattr(view, 'action', None)
+
+    def __enter__(self):
+        self.view.request = clone_request(self.request, self.method)
+        # For viewsets we also set the `.action` attribute.
+        action_map = getattr(self.view, 'action_map', {})
+        self.view.action = action_map.get(self.method.lower())
+        return self.view.request
+
+    def __exit__(self, *args, **kwarg):
+        self.view.request = self.request
+        self.view.action = self.action
+
+
+class WrappedAttributeError(Exception):
+    pass
+
+
+@contextmanager
+def wrap_attributeerrors():
+    """
+    Used to re-raise AttributeErrors caught during authentication, preventing
+    these errors from otherwise being handled by the attribute access protocol.
+    """
+    try:
+        yield
+    except AttributeError:
+        info = sys.exc_info()
+        exc = WrappedAttributeError(str(info[1]))
+        six.reraise(type(exc), exc, info[2])
+
+
+class Empty(object):
+    """
+    Placeholder for unset attributes.
+    Cannot use `None`, as that may be a valid value.
+    """
+    pass
+
+
+def _hasattr(obj, name):
+    return not getattr(obj, name) is Empty
+
+
+def clone_request(request, method):
+    """
+    Internal helper method to clone a request, replacing with a different
+    HTTP method.  Used for checking permissions against other methods.
+    """
+    ret = Request(request=request._request,
+                  parsers=request.parsers,
+                  authenticators=request.authenticators,
+                  negotiator=request.negotiator,
+                  parser_context=request.parser_context)
+    ret._data = request._data
+    ret._files = request._files
+    ret._full_data = request._full_data
+    ret._content_type = request._content_type
+    ret._stream = request._stream
+    ret.method = method
+    if hasattr(request, '_user'):
+        ret._user = request._user
+    if hasattr(request, '_auth'):
+        ret._auth = request._auth
+    if hasattr(request, '_authenticator'):
+        ret._authenticator = request._authenticator
+    if hasattr(request, 'accepted_renderer'):
+        ret.accepted_renderer = request.accepted_renderer
+    if hasattr(request, 'accepted_media_type'):
+        ret.accepted_media_type = request.accepted_media_type
+    if hasattr(request, 'version'):
+        ret.version = request.version
+    if hasattr(request, 'versioning_scheme'):
+        ret.versioning_scheme = request.versioning_scheme
+    return ret
+
+
+class ForcedAuthentication(object):
+    """
+    This authentication class is used if the test client or request factory
+    forcibly authenticated the request.
+    """
+
+    def __init__(self, force_user, force_token):
+        self.force_user = force_user
+        self.force_token = force_token
+
+    def authenticate(self, request):
+        return (self.force_user, self.force_token)
+
+
+class Request(object):
+    """
+    Wrapper allowing to enhance a standard `HttpRequest` instance.
+
+    Kwargs:
+        - request(HttpRequest). The original request instance.
+        - parsers_classes(list/tuple). The parsers to use for parsing the
+          request content.
+        - authentication_classes(list/tuple). The authentications used to try
+          authenticating the request's user.
+    """
+
+    def __init__(self, request, parsers=None, authenticators=None,
+                 negotiator=None, parser_context=None):
+        assert isinstance(request, HttpRequest), (
+            'The `request` argument must be an instance of '
+            '`django.http.HttpRequest`, not `{}.{}`.'
+            .format(request.__class__.__module__, request.__class__.__name__)
+        )
+
+        self._request = request
+        self.parsers = parsers or ()
+        self.authenticators = authenticators or ()
+        self.negotiator = negotiator or self._default_negotiator()
+        self.parser_context = parser_context
+        self._data = Empty
+        self._files = Empty
+        self._full_data = Empty
+        self._content_type = Empty
+        self._stream = Empty
+
+        if self.parser_context is None:
+            self.parser_context = {}
+        self.parser_context['request'] = self
+        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
+
+        force_user = getattr(request, '_force_auth_user', None)
+        force_token = getattr(request, '_force_auth_token', None)
+        if force_user is not None or force_token is not None:
+            forced_auth = ForcedAuthentication(force_user, force_token)
+            self.authenticators = (forced_auth,)
+
+    def _default_negotiator(self):
+        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
+
+    @property
+    def content_type(self):
+        meta = self._request.META
+        return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
+
+    @property
+    def stream(self):
+        """
+        Returns an object that may be used to stream the request content.
+        """
+        if not _hasattr(self, '_stream'):
+            self._load_stream()
+        return self._stream
+
+    @property
+    def query_params(self):
+        """
+        More semantically correct name for request.GET.
+        """
+        return self._request.GET
+
+    @property
+    def data(self):
+        if not _hasattr(self, '_full_data'):
+            self._load_data_and_files()
+        return self._full_data
+
+    @property
+    def user(self):
+        """
+        Returns the user associated with the current request, as authenticated
+        by the authentication classes provided to the request.
+        """
+        if not hasattr(self, '_user'):
+            with wrap_attributeerrors():
+                self._authenticate()
+        return self._user
+
+    @user.setter
+    def user(self, value):
+        """
+        Sets the user on the current request. This is necessary to maintain
+        compatibility with django.contrib.auth where the user property is
+        set in the login and logout functions.
+
+        Note that we also set the user on Django's underlying `HttpRequest`
+        instance, ensuring that it is available to any middleware in the stack.
+        """
+        self._user = value
+        self._request.user = value
+
+    @property
+    def auth(self):
+        """
+        Returns any non-user authentication information associated with the
+        request, such as an authentication token.
+        """
+        if not hasattr(self, '_auth'):
+            with wrap_attributeerrors():
+                self._authenticate()
+        return self._auth
+
+    @auth.setter
+    def auth(self, value):
+        """
+        Sets any non-user authentication information associated with the
+        request, such as an authentication token.
+        """
+        self._auth = value
+        self._request.auth = value
+
+    @property
+    def successful_authenticator(self):
+        """
+        Return the instance of the authentication instance class that was used
+        to authenticate the request, or `None`.
+        """
+        if not hasattr(self, '_authenticator'):
+            with wrap_attributeerrors():
+                self._authenticate()
+        return self._authenticator
+
+    def _load_data_and_files(self):
+        """
+        Parses the request content into `self.data`.
+        """
+        if not _hasattr(self, '_data'):
+            self._data, self._files = self._parse()
+            if self._files:
+                self._full_data = self._data.copy()
+                self._full_data.update(self._files)
+            else:
+                self._full_data = self._data
+
+            # if a form media type, copy data & files refs to the underlying
+            # http request so that closable objects are handled appropriately.
+            if is_form_media_type(self.content_type):
+                self._request._post = self.POST
+                self._request._files = self.FILES
+
+    def _load_stream(self):
+        """
+        Return the content body of the request, as a stream.
+        """
+        meta = self._request.META
+        try:
+            content_length = int(
+                meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
+            )
+        except (ValueError, TypeError):
+            content_length = 0
+
+        if content_length == 0:
+            self._stream = None
+        elif not self._request._read_started:
+            self._stream = self._request
+        else:
+            self._stream = io.BytesIO(self.body)
+
+    def _supports_form_parsing(self):
+        """
+        Return True if this requests supports parsing form data.
+        """
+        form_media = (
+            'application/x-www-form-urlencoded',
+            'multipart/form-data'
+        )
+        return any([parser.media_type in form_media for parser in self.parsers])
+
+    def _parse(self):
+        """
+        Parse the request content, returning a two-tuple of (data, files)
+
+        May raise an `UnsupportedMediaType`, or `ParseError` exception.
+        """
+        media_type = self.content_type
+        try:
+            stream = self.stream
+        except RawPostDataException:
+            if not hasattr(self._request, '_post'):
+                raise
+            # If request.POST has been accessed in middleware, and a method='POST'
+            # request was made with 'multipart/form-data', then the request stream
+            # will already have been exhausted.
+            if self._supports_form_parsing():
+                return (self._request.POST, self._request.FILES)
+            stream = None
+
+        if stream is None or media_type is None:
+            if media_type and is_form_media_type(media_type):
+                empty_data = QueryDict('', encoding=self._request._encoding)
+            else:
+                empty_data = {}
+            empty_files = MultiValueDict()
+            return (empty_data, empty_files)
+
+        parser = self.negotiator.select_parser(self, self.parsers)
+
+        if not parser:
+            raise exceptions.UnsupportedMediaType(media_type)
+
+        try:
+            parsed = parser.parse(stream, media_type, self.parser_context)
+        except Exception:
+            # If we get an exception during parsing, fill in empty data and
+            # re-raise.  Ensures we don't simply repeat the error when
+            # attempting to render the browsable renderer response, or when
+            # logging the request or similar.
+            self._data = QueryDict('', encoding=self._request._encoding)
+            self._files = MultiValueDict()
+            self._full_data = self._data
+            raise
+
+        # Parser classes may return the raw data, or a
+        # DataAndFiles object.  Unpack the result as required.
+        try:
+            return (parsed.data, parsed.files)
+        except AttributeError:
+            empty_files = MultiValueDict()
+            return (parsed, empty_files)
+
+    def _authenticate(self):
+        """
+        Attempt to authenticate the request using each authentication instance
+        in turn.
+        """
+        for authenticator in self.authenticators:
+            try:
+                user_auth_tuple = authenticator.authenticate(self)
+            except exceptions.APIException:
+                self._not_authenticated()
+                raise
+
+            if user_auth_tuple is not None:
+                self._authenticator = authenticator
+                self.user, self.auth = user_auth_tuple
+                return
+
+        self._not_authenticated()
+
+    def _not_authenticated(self):
+        """
+        Set authenticator, user & authtoken representing an unauthenticated request.
+
+        Defaults are None, AnonymousUser & None.
+        """
+        self._authenticator = None
+
+        if api_settings.UNAUTHENTICATED_USER:
+            self.user = api_settings.UNAUTHENTICATED_USER()
+        else:
+            self.user = None
+
+        if api_settings.UNAUTHENTICATED_TOKEN:
+            self.auth = api_settings.UNAUTHENTICATED_TOKEN()
+        else:
+            self.auth = None
+
+    def __getattr__(self, attr):
+        """
+        If an attribute does not exist on this instance, then we also attempt
+        to proxy it to the underlying HttpRequest object.
+        """
+        try:
+            return getattr(self._request, attr)
+        except AttributeError:
+            return self.__getattribute__(attr)
+
+    @property
+    def DATA(self):
+        raise NotImplementedError(
+            '`request.DATA` has been deprecated in favor of `request.data` '
+            'since version 3.0, and has been fully removed as of version 3.2.'
+        )
+
+    @property
+    def POST(self):
+        # Ensure that request.POST uses our request parsing.
+        if not _hasattr(self, '_data'):
+            self._load_data_and_files()
+        if is_form_media_type(self.content_type):
+            return self._data
+        return QueryDict('', encoding=self._request._encoding)
+
+    @property
+    def FILES(self):
+        # Leave this one alone for backwards compat with Django's request.FILES
+        # Different from the other two cases, which are not valid property
+        # names on the WSGIRequest class.
+        if not _hasattr(self, '_files'):
+            self._load_data_and_files()
+        return self._files
+
+    @property
+    def QUERY_PARAMS(self):
+        raise NotImplementedError(
+            '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
+            'since version 3.0, and has been fully removed as of version 3.2.'
+        )
+
+    def force_plaintext_errors(self, value):
+        # Hack to allow our exception handler to force choice of
+        # plaintext or html error responses.
+        self._request.is_ajax = lambda: value

+ 105 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/response.py

@@ -0,0 +1,105 @@
+"""
+The Response class in REST framework is similar to HTTPResponse, except that
+it is initialized with unrendered data, instead of a pre-rendered string.
+
+The appropriate renderer is called during Django's template response rendering.
+"""
+from __future__ import unicode_literals
+
+from django.template.response import SimpleTemplateResponse
+from django.utils import six
+from django.utils.six.moves.http_client import responses
+
+from rest_framework.serializers import Serializer
+
+
+class Response(SimpleTemplateResponse):
+    """
+    An HttpResponse that allows its data to be rendered into
+    arbitrary media types.
+    """
+
+    def __init__(self, data=None, status=None,
+                 template_name=None, headers=None,
+                 exception=False, content_type=None):
+        """
+        Alters the init arguments slightly.
+        For example, drop 'template_name', and instead use 'data'.
+
+        Setting 'renderer' and 'media_type' will typically be deferred,
+        For example being set automatically by the `APIView`.
+        """
+        super(Response, self).__init__(None, status=status)
+
+        if isinstance(data, Serializer):
+            msg = (
+                'You passed a Serializer instance as data, but '
+                'probably meant to pass serialized `.data` or '
+                '`.error`. representation.'
+            )
+            raise AssertionError(msg)
+
+        self.data = data
+        self.template_name = template_name
+        self.exception = exception
+        self.content_type = content_type
+
+        if headers:
+            for name, value in six.iteritems(headers):
+                self[name] = value
+
+    @property
+    def rendered_content(self):
+        renderer = getattr(self, 'accepted_renderer', None)
+        accepted_media_type = getattr(self, 'accepted_media_type', None)
+        context = getattr(self, 'renderer_context', None)
+
+        assert renderer, ".accepted_renderer not set on Response"
+        assert accepted_media_type, ".accepted_media_type not set on Response"
+        assert context is not None, ".renderer_context not set on Response"
+        context['response'] = self
+
+        media_type = renderer.media_type
+        charset = renderer.charset
+        content_type = self.content_type
+
+        if content_type is None and charset is not None:
+            content_type = "{0}; charset={1}".format(media_type, charset)
+        elif content_type is None:
+            content_type = media_type
+        self['Content-Type'] = content_type
+
+        ret = renderer.render(self.data, accepted_media_type, context)
+        if isinstance(ret, six.text_type):
+            assert charset, (
+                'renderer returned unicode, and did not specify '
+                'a charset value.'
+            )
+            return bytes(ret.encode(charset))
+
+        if not ret:
+            del self['Content-Type']
+
+        return ret
+
+    @property
+    def status_text(self):
+        """
+        Returns reason text corresponding to our HTTP response status code.
+        Provided for convenience.
+        """
+        return responses.get(self.status_code, '')
+
+    def __getstate__(self):
+        """
+        Remove attributes from the response that shouldn't be cached.
+        """
+        state = super(Response, self).__getstate__()
+        for key in (
+            'accepted_renderer', 'renderer_context', 'resolver_match',
+            'client', 'request', 'json', 'wsgi_request'
+        ):
+            if key in state:
+                del state[key]
+        state['_closable_objects'] = []
+        return state

+ 69 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/reverse.py

@@ -0,0 +1,69 @@
+"""
+Provide urlresolver functions that return fully qualified URLs or view names
+"""
+from __future__ import unicode_literals
+
+from django.urls import NoReverseMatch
+from django.urls import reverse as django_reverse
+from django.utils import six
+from django.utils.functional import lazy
+
+from rest_framework.settings import api_settings
+from rest_framework.utils.urls import replace_query_param
+
+
+def preserve_builtin_query_params(url, request=None):
+    """
+    Given an incoming request, and an outgoing URL representation,
+    append the value of any built-in query parameters.
+    """
+    if request is None:
+        return url
+
+    overrides = [
+        api_settings.URL_FORMAT_OVERRIDE,
+    ]
+
+    for param in overrides:
+        if param and (param in request.GET):
+            value = request.GET[param]
+            url = replace_query_param(url, param, value)
+
+    return url
+
+
+def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
+    """
+    If versioning is being used then we pass any `reverse` calls through
+    to the versioning scheme instance, so that the resulting URL
+    can be modified if needed.
+    """
+    scheme = getattr(request, 'versioning_scheme', None)
+    if scheme is not None:
+        try:
+            url = scheme.reverse(viewname, args, kwargs, request, format, **extra)
+        except NoReverseMatch:
+            # In case the versioning scheme reversal fails, fallback to the
+            # default implementation
+            url = _reverse(viewname, args, kwargs, request, format, **extra)
+    else:
+        url = _reverse(viewname, args, kwargs, request, format, **extra)
+
+    return preserve_builtin_query_params(url, request)
+
+
+def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
+    """
+    Same as `django.urls.reverse`, but optionally takes a request
+    and returns a fully qualified URL, using the request to get the base URL.
+    """
+    if format is not None:
+        kwargs = kwargs or {}
+        kwargs['format'] = format
+    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
+    if request:
+        return request.build_absolute_uri(url)
+    return url
+
+
+reverse_lazy = lazy(reverse, six.text_type)

+ 396 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/routers.py

@@ -0,0 +1,396 @@
+"""
+Routers provide a convenient and consistent way of automatically
+determining the URL conf for your API.
+
+They are used by simply instantiating a Router class, and then registering
+all the required ViewSets with that router.
+
+For example, you might have a `urls.py` that looks something like this:
+
+    router = routers.DefaultRouter()
+    router.register('users', UserViewSet, 'user')
+    router.register('accounts', AccountViewSet, 'account')
+
+    urlpatterns = router.urls
+"""
+from __future__ import unicode_literals
+
+import itertools
+import warnings
+from collections import OrderedDict, namedtuple
+
+from django.conf.urls import url
+from django.core.exceptions import ImproperlyConfigured
+from django.urls import NoReverseMatch
+from django.utils import six
+from django.utils.deprecation import RenameMethodsBase
+
+from rest_framework import (
+    RemovedInDRF310Warning, RemovedInDRF311Warning, views
+)
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework.schemas import SchemaGenerator
+from rest_framework.schemas.views import SchemaView
+from rest_framework.settings import api_settings
+from rest_framework.urlpatterns import format_suffix_patterns
+
+Route = namedtuple('Route', ['url', 'mapping', 'name', 'detail', 'initkwargs'])
+DynamicRoute = namedtuple('DynamicRoute', ['url', 'name', 'detail', 'initkwargs'])
+
+
+class DynamicDetailRoute(object):
+    def __new__(cls, url, name, initkwargs):
+        warnings.warn(
+            "`DynamicDetailRoute` is deprecated and will be removed in 3.10 "
+            "in favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
+            "`DynamicRoute(url, name, True, initkwargs)` instead.",
+            RemovedInDRF310Warning, stacklevel=2
+        )
+        return DynamicRoute(url, name, True, initkwargs)
+
+
+class DynamicListRoute(object):
+    def __new__(cls, url, name, initkwargs):
+        warnings.warn(
+            "`DynamicListRoute` is deprecated and will be removed in 3.10 in "
+            "favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
+            "`DynamicRoute(url, name, False, initkwargs)` instead.",
+            RemovedInDRF310Warning, stacklevel=2
+        )
+        return DynamicRoute(url, name, False, initkwargs)
+
+
+def escape_curly_brackets(url_path):
+    """
+    Double brackets in regex of url_path for escape string formatting
+    """
+    if ('{' and '}') in url_path:
+        url_path = url_path.replace('{', '{{').replace('}', '}}')
+    return url_path
+
+
+def flatten(list_of_lists):
+    """
+    Takes an iterable of iterables, returns a single iterable containing all items
+    """
+    return itertools.chain(*list_of_lists)
+
+
+class RenameRouterMethods(RenameMethodsBase):
+    renamed_methods = (
+        ('get_default_base_name', 'get_default_basename', RemovedInDRF311Warning),
+    )
+
+
+class BaseRouter(six.with_metaclass(RenameRouterMethods)):
+    def __init__(self):
+        self.registry = []
+
+    def register(self, prefix, viewset, basename=None, base_name=None):
+        if base_name is not None:
+            msg = "The `base_name` argument is pending deprecation in favor of `basename`."
+            warnings.warn(msg, RemovedInDRF311Warning, 2)
+
+        assert not (basename and base_name), (
+            "Do not provide both the `basename` and `base_name` arguments.")
+
+        if basename is None:
+            basename = base_name
+
+        if basename is None:
+            basename = self.get_default_basename(viewset)
+        self.registry.append((prefix, viewset, basename))
+
+        # invalidate the urls cache
+        if hasattr(self, '_urls'):
+            del self._urls
+
+    def get_default_basename(self, viewset):
+        """
+        If `basename` is not specified, attempt to automatically determine
+        it from the viewset.
+        """
+        raise NotImplementedError('get_default_basename must be overridden')
+
+    def get_urls(self):
+        """
+        Return a list of URL patterns, given the registered viewsets.
+        """
+        raise NotImplementedError('get_urls must be overridden')
+
+    @property
+    def urls(self):
+        if not hasattr(self, '_urls'):
+            self._urls = self.get_urls()
+        return self._urls
+
+
+class SimpleRouter(BaseRouter):
+
+    routes = [
+        # List route.
+        Route(
+            url=r'^{prefix}{trailing_slash}$',
+            mapping={
+                'get': 'list',
+                'post': 'create'
+            },
+            name='{basename}-list',
+            detail=False,
+            initkwargs={'suffix': 'List'}
+        ),
+        # Dynamically generated list routes. Generated using
+        # @action(detail=False) decorator on methods of the viewset.
+        DynamicRoute(
+            url=r'^{prefix}/{url_path}{trailing_slash}$',
+            name='{basename}-{url_name}',
+            detail=False,
+            initkwargs={}
+        ),
+        # Detail route.
+        Route(
+            url=r'^{prefix}/{lookup}{trailing_slash}$',
+            mapping={
+                'get': 'retrieve',
+                'put': 'update',
+                'patch': 'partial_update',
+                'delete': 'destroy'
+            },
+            name='{basename}-detail',
+            detail=True,
+            initkwargs={'suffix': 'Instance'}
+        ),
+        # Dynamically generated detail routes. Generated using
+        # @action(detail=True) decorator on methods of the viewset.
+        DynamicRoute(
+            url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
+            name='{basename}-{url_name}',
+            detail=True,
+            initkwargs={}
+        ),
+    ]
+
+    def __init__(self, trailing_slash=True):
+        self.trailing_slash = '/' if trailing_slash else ''
+        super(SimpleRouter, self).__init__()
+
+    def get_default_basename(self, viewset):
+        """
+        If `basename` is not specified, attempt to automatically determine
+        it from the viewset.
+        """
+        queryset = getattr(viewset, 'queryset', None)
+
+        assert queryset is not None, '`basename` argument not specified, and could ' \
+            'not automatically determine the name from the viewset, as ' \
+            'it does not have a `.queryset` attribute.'
+
+        return queryset.model._meta.object_name.lower()
+
+    def get_routes(self, viewset):
+        """
+        Augment `self.routes` with any dynamically generated routes.
+
+        Returns a list of the Route namedtuple.
+        """
+        # converting to list as iterables are good for one pass, known host needs to be checked again and again for
+        # different functions.
+        known_actions = list(flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]))
+        extra_actions = viewset.get_extra_actions()
+
+        # checking action names against the known actions list
+        not_allowed = [
+            action.__name__ for action in extra_actions
+            if action.__name__ in known_actions
+        ]
+        if not_allowed:
+            msg = ('Cannot use the @action decorator on the following '
+                   'methods, as they are existing routes: %s')
+            raise ImproperlyConfigured(msg % ', '.join(not_allowed))
+
+        # partition detail and list actions
+        detail_actions = [action for action in extra_actions if action.detail]
+        list_actions = [action for action in extra_actions if not action.detail]
+
+        routes = []
+        for route in self.routes:
+            if isinstance(route, DynamicRoute) and route.detail:
+                routes += [self._get_dynamic_route(route, action) for action in detail_actions]
+            elif isinstance(route, DynamicRoute) and not route.detail:
+                routes += [self._get_dynamic_route(route, action) for action in list_actions]
+            else:
+                routes.append(route)
+
+        return routes
+
+    def _get_dynamic_route(self, route, action):
+        initkwargs = route.initkwargs.copy()
+        initkwargs.update(action.kwargs)
+
+        url_path = escape_curly_brackets(action.url_path)
+
+        return Route(
+            url=route.url.replace('{url_path}', url_path),
+            mapping=action.mapping,
+            name=route.name.replace('{url_name}', action.url_name),
+            detail=route.detail,
+            initkwargs=initkwargs,
+        )
+
+    def get_method_map(self, viewset, method_map):
+        """
+        Given a viewset, and a mapping of http methods to actions,
+        return a new mapping which only includes any mappings that
+        are actually implemented by the viewset.
+        """
+        bound_methods = {}
+        for method, action in method_map.items():
+            if hasattr(viewset, action):
+                bound_methods[method] = action
+        return bound_methods
+
+    def get_lookup_regex(self, viewset, lookup_prefix=''):
+        """
+        Given a viewset, return the portion of URL regex that is used
+        to match against a single instance.
+
+        Note that lookup_prefix is not used directly inside REST rest_framework
+        itself, but is required in order to nicely support nested router
+        implementations, such as drf-nested-routers.
+
+        https://github.com/alanjds/drf-nested-routers
+        """
+        base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
+        # Use `pk` as default field, unset set.  Default regex should not
+        # consume `.json` style suffixes and should break at '/' boundaries.
+        lookup_field = getattr(viewset, 'lookup_field', 'pk')
+        lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field
+        lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
+        return base_regex.format(
+            lookup_prefix=lookup_prefix,
+            lookup_url_kwarg=lookup_url_kwarg,
+            lookup_value=lookup_value
+        )
+
+    def get_urls(self):
+        """
+        Use the registered viewsets to generate a list of URL patterns.
+        """
+        ret = []
+
+        for prefix, viewset, basename in self.registry:
+            lookup = self.get_lookup_regex(viewset)
+            routes = self.get_routes(viewset)
+
+            for route in routes:
+
+                # Only actions which actually exist on the viewset will be bound
+                mapping = self.get_method_map(viewset, route.mapping)
+                if not mapping:
+                    continue
+
+                # Build the url pattern
+                regex = route.url.format(
+                    prefix=prefix,
+                    lookup=lookup,
+                    trailing_slash=self.trailing_slash
+                )
+
+                # If there is no prefix, the first part of the url is probably
+                #   controlled by project's urls.py and the router is in an app,
+                #   so a slash in the beginning will (A) cause Django to give
+                #   warnings and (B) generate URLS that will require using '//'.
+                if not prefix and regex[:2] == '^/':
+                    regex = '^' + regex[2:]
+
+                initkwargs = route.initkwargs.copy()
+                initkwargs.update({
+                    'basename': basename,
+                    'detail': route.detail,
+                })
+
+                view = viewset.as_view(mapping, **initkwargs)
+                name = route.name.format(basename=basename)
+                ret.append(url(regex, view, name=name))
+
+        return ret
+
+
+class APIRootView(views.APIView):
+    """
+    The default basic root view for DefaultRouter
+    """
+    _ignore_model_permissions = True
+    schema = None  # exclude from schema
+    api_root_dict = None
+
+    def get(self, request, *args, **kwargs):
+        # Return a plain {"name": "hyperlink"} response.
+        ret = OrderedDict()
+        namespace = request.resolver_match.namespace
+        for key, url_name in self.api_root_dict.items():
+            if namespace:
+                url_name = namespace + ':' + url_name
+            try:
+                ret[key] = reverse(
+                    url_name,
+                    args=args,
+                    kwargs=kwargs,
+                    request=request,
+                    format=kwargs.get('format', None)
+                )
+            except NoReverseMatch:
+                # Don't bail out if eg. no list routes exist, only detail routes.
+                continue
+
+        return Response(ret)
+
+
+class DefaultRouter(SimpleRouter):
+    """
+    The default router extends the SimpleRouter, but also adds in a default
+    API root view, and adds format suffix patterns to the URLs.
+    """
+    include_root_view = True
+    include_format_suffixes = True
+    root_view_name = 'api-root'
+    default_schema_renderers = None
+    APIRootView = APIRootView
+    APISchemaView = SchemaView
+    SchemaGenerator = SchemaGenerator
+
+    def __init__(self, *args, **kwargs):
+        if 'root_renderers' in kwargs:
+            self.root_renderers = kwargs.pop('root_renderers')
+        else:
+            self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)
+        super(DefaultRouter, self).__init__(*args, **kwargs)
+
+    def get_api_root_view(self, api_urls=None):
+        """
+        Return a basic root view.
+        """
+        api_root_dict = OrderedDict()
+        list_name = self.routes[0].name
+        for prefix, viewset, basename in self.registry:
+            api_root_dict[prefix] = list_name.format(basename=basename)
+
+        return self.APIRootView.as_view(api_root_dict=api_root_dict)
+
+    def get_urls(self):
+        """
+        Generate the list of URL patterns, including a default root view
+        for the API, and appending `.json` style format suffixes.
+        """
+        urls = super(DefaultRouter, self).get_urls()
+
+        if self.include_root_view:
+            view = self.get_api_root_view(api_urls=urls)
+            root_url = url(r'^$', view, name=self.root_view_name)
+            urls.append(root_url)
+
+        if self.include_format_suffixes:
+            urls = format_suffix_patterns(urls)
+
+        return urls

+ 49 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/__init__.py

@@ -0,0 +1,49 @@
+"""
+rest_framework.schemas
+
+schemas:
+    __init__.py
+    generators.py   # Top-down schema generation
+    inspectors.py   # Per-endpoint view introspection
+    utils.py        # Shared helper functions
+    views.py        # Houses `SchemaView`, `APIView` subclass.
+
+We expose a minimal "public" API directly from `schemas`. This covers the
+basic use-cases:
+
+    from rest_framework.schemas import (
+        AutoSchema,
+        ManualSchema,
+        get_schema_view,
+        SchemaGenerator,
+    )
+
+Other access should target the submodules directly
+"""
+from rest_framework.settings import api_settings
+
+from .generators import SchemaGenerator
+from .inspectors import AutoSchema, DefaultSchema, ManualSchema  # noqa
+
+
+def get_schema_view(
+        title=None, url=None, description=None, urlconf=None, renderer_classes=None,
+        public=False, patterns=None, generator_class=SchemaGenerator,
+        authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
+        permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
+    """
+    Return a schema view.
+    """
+    # Avoid import cycle on APIView
+    from .views import SchemaView
+    generator = generator_class(
+        title=title, url=url, description=description,
+        urlconf=urlconf, patterns=patterns,
+    )
+    return SchemaView.as_view(
+        renderer_classes=renderer_classes,
+        schema_generator=generator,
+        public=public,
+        authentication_classes=authentication_classes,
+        permission_classes=permission_classes,
+    )

+ 454 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/generators.py

@@ -0,0 +1,454 @@
+"""
+generators.py   # Top-down schema generation
+
+See schemas.__init__.py for package overview.
+"""
+import re
+from collections import Counter, OrderedDict
+from importlib import import_module
+
+from django.conf import settings
+from django.contrib.admindocs.views import simplify_regex
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
+from django.utils import six
+
+from rest_framework import exceptions
+from rest_framework.compat import (
+    URLPattern, URLResolver, coreapi, coreschema, get_original_route
+)
+from rest_framework.request import clone_request
+from rest_framework.settings import api_settings
+from rest_framework.utils.model_meta import _get_pk
+
+from .utils import is_list_view
+
+
+def common_path(paths):
+    split_paths = [path.strip('/').split('/') for path in paths]
+    s1 = min(split_paths)
+    s2 = max(split_paths)
+    common = s1
+    for i, c in enumerate(s1):
+        if c != s2[i]:
+            common = s1[:i]
+            break
+    return '/' + '/'.join(common)
+
+
+def get_pk_name(model):
+    meta = model._meta.concrete_model._meta
+    return _get_pk(meta).name
+
+
+def is_api_view(callback):
+    """
+    Return `True` if the given view callback is a REST framework view/viewset.
+    """
+    # Avoid import cycle on APIView
+    from rest_framework.views import APIView
+    cls = getattr(callback, 'cls', None)
+    return (cls is not None) and issubclass(cls, APIView)
+
+
+INSERT_INTO_COLLISION_FMT = """
+Schema Naming Collision.
+
+coreapi.Link for URL path {value_url} cannot be inserted into schema.
+Position conflicts with coreapi.Link for URL path {target_url}.
+
+Attempted to insert link with keys: {keys}.
+
+Adjust URLs to avoid naming collision or override `SchemaGenerator.get_keys()`
+to customise schema structure.
+"""
+
+
+class LinkNode(OrderedDict):
+    def __init__(self):
+        self.links = []
+        self.methods_counter = Counter()
+        super(LinkNode, self).__init__()
+
+    def get_available_key(self, preferred_key):
+        if preferred_key not in self:
+            return preferred_key
+
+        while True:
+            current_val = self.methods_counter[preferred_key]
+            self.methods_counter[preferred_key] += 1
+
+            key = '{}_{}'.format(preferred_key, current_val)
+            if key not in self:
+                return key
+
+
+def insert_into(target, keys, value):
+    """
+    Nested dictionary insertion.
+
+    >>> example = {}
+    >>> insert_into(example, ['a', 'b', 'c'], 123)
+    >>> example
+    LinkNode({'a': LinkNode({'b': LinkNode({'c': LinkNode(links=[123])}}})))
+    """
+    for key in keys[:-1]:
+        if key not in target:
+            target[key] = LinkNode()
+        target = target[key]
+
+    try:
+        target.links.append((keys[-1], value))
+    except TypeError:
+        msg = INSERT_INTO_COLLISION_FMT.format(
+            value_url=value.url,
+            target_url=target.url,
+            keys=keys
+        )
+        raise ValueError(msg)
+
+
+def distribute_links(obj):
+    for key, value in obj.items():
+        distribute_links(value)
+
+    for preferred_key, link in obj.links:
+        key = obj.get_available_key(preferred_key)
+        obj[key] = link
+
+
+def is_custom_action(action):
+    return action not in {
+        'retrieve', 'list', 'create', 'update', 'partial_update', 'destroy'
+    }
+
+
+def endpoint_ordering(endpoint):
+    path, method, callback = endpoint
+    method_priority = {
+        'GET': 0,
+        'POST': 1,
+        'PUT': 2,
+        'PATCH': 3,
+        'DELETE': 4
+    }.get(method, 5)
+    return (path, method_priority)
+
+
+_PATH_PARAMETER_COMPONENT_RE = re.compile(
+    r'<(?:(?P<converter>[^>:]+):)?(?P<parameter>\w+)>'
+)
+
+
+class EndpointEnumerator(object):
+    """
+    A class to determine the available API endpoints that a project exposes.
+    """
+    def __init__(self, patterns=None, urlconf=None):
+        if patterns is None:
+            if urlconf is None:
+                # Use the default Django URL conf
+                urlconf = settings.ROOT_URLCONF
+
+            # Load the given URLconf module
+            if isinstance(urlconf, six.string_types):
+                urls = import_module(urlconf)
+            else:
+                urls = urlconf
+            patterns = urls.urlpatterns
+
+        self.patterns = patterns
+
+    def get_api_endpoints(self, patterns=None, prefix=''):
+        """
+        Return a list of all available API endpoints by inspecting the URL conf.
+        """
+        if patterns is None:
+            patterns = self.patterns
+
+        api_endpoints = []
+
+        for pattern in patterns:
+            path_regex = prefix + get_original_route(pattern)
+            if isinstance(pattern, URLPattern):
+                path = self.get_path_from_regex(path_regex)
+                callback = pattern.callback
+                if self.should_include_endpoint(path, callback):
+                    for method in self.get_allowed_methods(callback):
+                        endpoint = (path, method, callback)
+                        api_endpoints.append(endpoint)
+
+            elif isinstance(pattern, URLResolver):
+                nested_endpoints = self.get_api_endpoints(
+                    patterns=pattern.url_patterns,
+                    prefix=path_regex
+                )
+                api_endpoints.extend(nested_endpoints)
+
+        api_endpoints = sorted(api_endpoints, key=endpoint_ordering)
+
+        return api_endpoints
+
+    def get_path_from_regex(self, path_regex):
+        """
+        Given a URL conf regex, return a URI template string.
+        """
+        path = simplify_regex(path_regex)
+
+        # Strip Django 2.0 convertors as they are incompatible with uritemplate format
+        path = re.sub(_PATH_PARAMETER_COMPONENT_RE, r'{\g<parameter>}', path)
+        return path
+
+    def should_include_endpoint(self, path, callback):
+        """
+        Return `True` if the given endpoint should be included.
+        """
+        if not is_api_view(callback):
+            return False  # Ignore anything except REST framework views.
+
+        if callback.cls.schema is None:
+            return False
+
+        if 'schema' in callback.initkwargs:
+            if callback.initkwargs['schema'] is None:
+                return False
+
+        if path.endswith('.{format}') or path.endswith('.{format}/'):
+            return False  # Ignore .json style URLs.
+
+        return True
+
+    def get_allowed_methods(self, callback):
+        """
+        Return a list of the valid HTTP methods for this endpoint.
+        """
+        if hasattr(callback, 'actions'):
+            actions = set(callback.actions)
+            http_method_names = set(callback.cls.http_method_names)
+            methods = [method.upper() for method in actions & http_method_names]
+        else:
+            methods = callback.cls().allowed_methods
+
+        return [method for method in methods if method not in ('OPTIONS', 'HEAD')]
+
+
+class SchemaGenerator(object):
+    # Map HTTP methods onto actions.
+    default_mapping = {
+        'get': 'retrieve',
+        'post': 'create',
+        'put': 'update',
+        'patch': 'partial_update',
+        'delete': 'destroy',
+    }
+    endpoint_inspector_cls = EndpointEnumerator
+
+    # Map the method names we use for viewset actions onto external schema names.
+    # These give us names that are more suitable for the external representation.
+    # Set by 'SCHEMA_COERCE_METHOD_NAMES'.
+    coerce_method_names = None
+
+    # 'pk' isn't great as an externally exposed name for an identifier,
+    # so by default we prefer to use the actual model field name for schemas.
+    # Set by 'SCHEMA_COERCE_PATH_PK'.
+    coerce_path_pk = None
+
+    def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None):
+        assert coreapi, '`coreapi` must be installed for schema support.'
+        assert coreschema, '`coreschema` must be installed for schema support.'
+
+        if url and not url.endswith('/'):
+            url += '/'
+
+        self.coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES
+        self.coerce_path_pk = api_settings.SCHEMA_COERCE_PATH_PK
+
+        self.patterns = patterns
+        self.urlconf = urlconf
+        self.title = title
+        self.description = description
+        self.url = url
+        self.endpoints = None
+
+    def get_schema(self, request=None, public=False):
+        """
+        Generate a `coreapi.Document` representing the API schema.
+        """
+        if self.endpoints is None:
+            inspector = self.endpoint_inspector_cls(self.patterns, self.urlconf)
+            self.endpoints = inspector.get_api_endpoints()
+
+        links = self.get_links(None if public else request)
+        if not links:
+            return None
+
+        url = self.url
+        if not url and request is not None:
+            url = request.build_absolute_uri()
+
+        distribute_links(links)
+        return coreapi.Document(
+            title=self.title, description=self.description,
+            url=url, content=links
+        )
+
+    def get_links(self, request=None):
+        """
+        Return a dictionary containing all the links that should be
+        included in the API schema.
+        """
+        links = LinkNode()
+
+        # Generate (path, method, view) given (path, method, callback).
+        paths = []
+        view_endpoints = []
+        for path, method, callback in self.endpoints:
+            view = self.create_view(callback, method, request)
+            path = self.coerce_path(path, method, view)
+            paths.append(path)
+            view_endpoints.append((path, method, view))
+
+        # Only generate the path prefix for paths that will be included
+        if not paths:
+            return None
+        prefix = self.determine_path_prefix(paths)
+
+        for path, method, view in view_endpoints:
+            if not self.has_view_permissions(path, method, view):
+                continue
+            link = view.schema.get_link(path, method, base_url=self.url)
+            subpath = path[len(prefix):]
+            keys = self.get_keys(subpath, method, view)
+            insert_into(links, keys, link)
+
+        return links
+
+    # Methods used when we generate a view instance from the raw callback...
+
+    def determine_path_prefix(self, paths):
+        """
+        Given a list of all paths, return the common prefix which should be
+        discounted when generating a schema structure.
+
+        This will be the longest common string that does not include that last
+        component of the URL, or the last component before a path parameter.
+
+        For example:
+
+        /api/v1/users/
+        /api/v1/users/{pk}/
+
+        The path prefix is '/api/v1/'
+        """
+        prefixes = []
+        for path in paths:
+            components = path.strip('/').split('/')
+            initial_components = []
+            for component in components:
+                if '{' in component:
+                    break
+                initial_components.append(component)
+            prefix = '/'.join(initial_components[:-1])
+            if not prefix:
+                # We can just break early in the case that there's at least
+                # one URL that doesn't have a path prefix.
+                return '/'
+            prefixes.append('/' + prefix + '/')
+        return common_path(prefixes)
+
+    def create_view(self, callback, method, request=None):
+        """
+        Given a callback, return an actual view instance.
+        """
+        view = callback.cls(**getattr(callback, 'initkwargs', {}))
+        view.args = ()
+        view.kwargs = {}
+        view.format_kwarg = None
+        view.request = None
+        view.action_map = getattr(callback, 'actions', None)
+
+        actions = getattr(callback, 'actions', None)
+        if actions is not None:
+            if method == 'OPTIONS':
+                view.action = 'metadata'
+            else:
+                view.action = actions.get(method.lower())
+
+        if request is not None:
+            view.request = clone_request(request, method)
+
+        return view
+
+    def has_view_permissions(self, path, method, view):
+        """
+        Return `True` if the incoming request has the correct view permissions.
+        """
+        if view.request is None:
+            return True
+
+        try:
+            view.check_permissions(view.request)
+        except (exceptions.APIException, Http404, PermissionDenied):
+            return False
+        return True
+
+    def coerce_path(self, path, method, view):
+        """
+        Coerce {pk} path arguments into the name of the model field,
+        where possible. This is cleaner for an external representation.
+        (Ie. "this is an identifier", not "this is a database primary key")
+        """
+        if not self.coerce_path_pk or '{pk}' not in path:
+            return path
+        model = getattr(getattr(view, 'queryset', None), 'model', None)
+        if model:
+            field_name = get_pk_name(model)
+        else:
+            field_name = 'id'
+        return path.replace('{pk}', '{%s}' % field_name)
+
+    # Method for generating the link layout....
+
+    def get_keys(self, subpath, method, view):
+        """
+        Return a list of keys that should be used to layout a link within
+        the schema document.
+
+        /users/                   ("users", "list"), ("users", "create")
+        /users/{pk}/              ("users", "read"), ("users", "update"), ("users", "delete")
+        /users/enabled/           ("users", "enabled")  # custom viewset list action
+        /users/{pk}/star/         ("users", "star")     # custom viewset detail action
+        /users/{pk}/groups/       ("users", "groups", "list"), ("users", "groups", "create")
+        /users/{pk}/groups/{pk}/  ("users", "groups", "read"), ("users", "groups", "update"), ("users", "groups", "delete")
+        """
+        if hasattr(view, 'action'):
+            # Viewsets have explicitly named actions.
+            action = view.action
+        else:
+            # Views have no associated action, so we determine one from the method.
+            if is_list_view(subpath, method, view):
+                action = 'list'
+            else:
+                action = self.default_mapping[method.lower()]
+
+        named_path_components = [
+            component for component
+            in subpath.strip('/').split('/')
+            if '{' not in component
+        ]
+
+        if is_custom_action(action):
+            # Custom action, eg "/users/{pk}/activate/", "/users/active/"
+            if len(view.action_map) > 1:
+                action = self.default_mapping[method.lower()]
+                if action in self.coerce_method_names:
+                    action = self.coerce_method_names[action]
+                return named_path_components + [action]
+            else:
+                return named_path_components[:-1] + [action]
+
+        if action in self.coerce_method_names:
+            action = self.coerce_method_names[action]
+
+        # Default action, eg "/users/", "/users/{pk}/"
+        return named_path_components + [action]

+ 511 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/inspectors.py

@@ -0,0 +1,511 @@
+# -*- coding: utf-8 -*-
+"""
+inspectors.py   # Per-endpoint view introspection
+
+See schemas.__init__.py for package overview.
+"""
+import re
+import warnings
+from collections import OrderedDict
+from weakref import WeakKeyDictionary
+
+from django.db import models
+from django.utils.encoding import force_text, smart_text
+from django.utils.six.moves.urllib import parse as urlparse
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework import exceptions, serializers
+from rest_framework.compat import coreapi, coreschema, uritemplate
+from rest_framework.settings import api_settings
+from rest_framework.utils import formatting
+
+from .utils import is_list_view
+
+header_regex = re.compile('^[a-zA-Z][0-9A-Za-z_]*:')
+
+
+def field_to_schema(field):
+    title = force_text(field.label) if field.label else ''
+    description = force_text(field.help_text) if field.help_text else ''
+
+    if isinstance(field, (serializers.ListSerializer, serializers.ListField)):
+        child_schema = field_to_schema(field.child)
+        return coreschema.Array(
+            items=child_schema,
+            title=title,
+            description=description
+        )
+    elif isinstance(field, serializers.DictField):
+        return coreschema.Object(
+            title=title,
+            description=description
+        )
+    elif isinstance(field, serializers.Serializer):
+        return coreschema.Object(
+            properties=OrderedDict([
+                (key, field_to_schema(value))
+                for key, value
+                in field.fields.items()
+            ]),
+            title=title,
+            description=description
+        )
+    elif isinstance(field, serializers.ManyRelatedField):
+        related_field_schema = field_to_schema(field.child_relation)
+
+        return coreschema.Array(
+            items=related_field_schema,
+            title=title,
+            description=description
+        )
+    elif isinstance(field, serializers.PrimaryKeyRelatedField):
+        schema_cls = coreschema.String
+        model = getattr(field.queryset, 'model', None)
+        if model is not None:
+            model_field = model._meta.pk
+            if isinstance(model_field, models.AutoField):
+                schema_cls = coreschema.Integer
+        return schema_cls(title=title, description=description)
+    elif isinstance(field, serializers.RelatedField):
+        return coreschema.String(title=title, description=description)
+    elif isinstance(field, serializers.MultipleChoiceField):
+        return coreschema.Array(
+            items=coreschema.Enum(enum=list(field.choices)),
+            title=title,
+            description=description
+        )
+    elif isinstance(field, serializers.ChoiceField):
+        return coreschema.Enum(
+            enum=list(field.choices),
+            title=title,
+            description=description
+        )
+    elif isinstance(field, serializers.BooleanField):
+        return coreschema.Boolean(title=title, description=description)
+    elif isinstance(field, (serializers.DecimalField, serializers.FloatField)):
+        return coreschema.Number(title=title, description=description)
+    elif isinstance(field, serializers.IntegerField):
+        return coreschema.Integer(title=title, description=description)
+    elif isinstance(field, serializers.DateField):
+        return coreschema.String(
+            title=title,
+            description=description,
+            format='date'
+        )
+    elif isinstance(field, serializers.DateTimeField):
+        return coreschema.String(
+            title=title,
+            description=description,
+            format='date-time'
+        )
+    elif isinstance(field, serializers.JSONField):
+        return coreschema.Object(title=title, description=description)
+
+    if field.style.get('base_template') == 'textarea.html':
+        return coreschema.String(
+            title=title,
+            description=description,
+            format='textarea'
+        )
+
+    return coreschema.String(title=title, description=description)
+
+
+def get_pk_description(model, model_field):
+    if isinstance(model_field, models.AutoField):
+        value_type = _('unique integer value')
+    elif isinstance(model_field, models.UUIDField):
+        value_type = _('UUID string')
+    else:
+        value_type = _('unique value')
+
+    return _('A {value_type} identifying this {name}.').format(
+        value_type=value_type,
+        name=model._meta.verbose_name,
+    )
+
+
+class ViewInspector(object):
+    """
+    Descriptor class on APIView.
+
+    Provide subclass for per-view schema generation
+    """
+
+    def __init__(self):
+        self.instance_schemas = WeakKeyDictionary()
+
+    def __get__(self, instance, owner):
+        """
+        Enables `ViewInspector` as a Python _Descriptor_.
+
+        This is how `view.schema` knows about `view`.
+
+        `__get__` is called when the descriptor is accessed on the owner.
+        (That will be when view.schema is called in our case.)
+
+        `owner` is always the owner class. (An APIView, or subclass for us.)
+        `instance` is the view instance or `None` if accessed from the class,
+        rather than an instance.
+
+        See: https://docs.python.org/3/howto/descriptor.html for info on
+        descriptor usage.
+        """
+        if instance in self.instance_schemas:
+            return self.instance_schemas[instance]
+
+        self.view = instance
+        return self
+
+    def __set__(self, instance, other):
+        self.instance_schemas[instance] = other
+        if other is not None:
+            other.view = instance
+
+    @property
+    def view(self):
+        """View property."""
+        assert self._view is not None, (
+            "Schema generation REQUIRES a view instance. (Hint: you accessed "
+            "`schema` from the view class rather than an instance.)"
+        )
+        return self._view
+
+    @view.setter
+    def view(self, value):
+        self._view = value
+
+    @view.deleter
+    def view(self):
+        self._view = None
+
+    def get_link(self, path, method, base_url):
+        """
+        Generate `coreapi.Link` for self.view, path and method.
+
+        This is the main _public_ access point.
+
+        Parameters:
+
+        * path: Route path for view from URLConf.
+        * method: The HTTP request method.
+        * base_url: The project "mount point" as given to SchemaGenerator
+        """
+        raise NotImplementedError(".get_link() must be overridden.")
+
+
+class AutoSchema(ViewInspector):
+    """
+    Default inspector for APIView
+
+    Responsible for per-view introspection and schema generation.
+    """
+    def __init__(self, manual_fields=None):
+        """
+        Parameters:
+
+        * `manual_fields`: list of `coreapi.Field` instances that
+            will be added to auto-generated fields, overwriting on `Field.name`
+        """
+        super(AutoSchema, self).__init__()
+        if manual_fields is None:
+            manual_fields = []
+        self._manual_fields = manual_fields
+
+    def get_link(self, path, method, base_url):
+        fields = self.get_path_fields(path, method)
+        fields += self.get_serializer_fields(path, method)
+        fields += self.get_pagination_fields(path, method)
+        fields += self.get_filter_fields(path, method)
+
+        manual_fields = self.get_manual_fields(path, method)
+        fields = self.update_fields(fields, manual_fields)
+
+        if fields and any([field.location in ('form', 'body') for field in fields]):
+            encoding = self.get_encoding(path, method)
+        else:
+            encoding = None
+
+        description = self.get_description(path, method)
+
+        if base_url and path.startswith('/'):
+            path = path[1:]
+
+        return coreapi.Link(
+            url=urlparse.urljoin(base_url, path),
+            action=method.lower(),
+            encoding=encoding,
+            fields=fields,
+            description=description
+        )
+
+    def get_description(self, path, method):
+        """
+        Determine a link description.
+
+        This will be based on the method docstring if one exists,
+        or else the class docstring.
+        """
+        view = self.view
+
+        method_name = getattr(view, 'action', method.lower())
+        method_docstring = getattr(view, method_name, None).__doc__
+        if method_docstring:
+            # An explicit docstring on the method or action.
+            return self._get_description_section(view, method.lower(), formatting.dedent(smart_text(method_docstring)))
+        else:
+            return self._get_description_section(view, getattr(view, 'action', method.lower()), view.get_view_description())
+
+    def _get_description_section(self, view, header, description):
+        lines = [line for line in description.splitlines()]
+        current_section = ''
+        sections = {'': ''}
+
+        for line in lines:
+            if header_regex.match(line):
+                current_section, seperator, lead = line.partition(':')
+                sections[current_section] = lead.strip()
+            else:
+                sections[current_section] += '\n' + line
+
+        # TODO: SCHEMA_COERCE_METHOD_NAMES appears here and in `SchemaGenerator.get_keys`
+        coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES
+        if header in sections:
+            return sections[header].strip()
+        if header in coerce_method_names:
+            if coerce_method_names[header] in sections:
+                return sections[coerce_method_names[header]].strip()
+        return sections[''].strip()
+
+    def get_path_fields(self, path, method):
+        """
+        Return a list of `coreapi.Field` instances corresponding to any
+        templated path variables.
+        """
+        view = self.view
+        model = getattr(getattr(view, 'queryset', None), 'model', None)
+        fields = []
+
+        for variable in uritemplate.variables(path):
+            title = ''
+            description = ''
+            schema_cls = coreschema.String
+            kwargs = {}
+            if model is not None:
+                # Attempt to infer a field description if possible.
+                try:
+                    model_field = model._meta.get_field(variable)
+                except Exception:
+                    model_field = None
+
+                if model_field is not None and model_field.verbose_name:
+                    title = force_text(model_field.verbose_name)
+
+                if model_field is not None and model_field.help_text:
+                    description = force_text(model_field.help_text)
+                elif model_field is not None and model_field.primary_key:
+                    description = get_pk_description(model, model_field)
+
+                if hasattr(view, 'lookup_value_regex') and view.lookup_field == variable:
+                    kwargs['pattern'] = view.lookup_value_regex
+                elif isinstance(model_field, models.AutoField):
+                    schema_cls = coreschema.Integer
+
+            field = coreapi.Field(
+                name=variable,
+                location='path',
+                required=True,
+                schema=schema_cls(title=title, description=description, **kwargs)
+            )
+            fields.append(field)
+
+        return fields
+
+    def get_serializer_fields(self, path, method):
+        """
+        Return a list of `coreapi.Field` instances corresponding to any
+        request body input, as determined by the serializer class.
+        """
+        view = self.view
+
+        if method not in ('PUT', 'PATCH', 'POST'):
+            return []
+
+        if not hasattr(view, 'get_serializer'):
+            return []
+
+        try:
+            serializer = view.get_serializer()
+        except exceptions.APIException:
+            serializer = None
+            warnings.warn('{}.get_serializer() raised an exception during '
+                          'schema generation. Serializer fields will not be '
+                          'generated for {} {}.'
+                          .format(view.__class__.__name__, method, path))
+
+        if isinstance(serializer, serializers.ListSerializer):
+            return [
+                coreapi.Field(
+                    name='data',
+                    location='body',
+                    required=True,
+                    schema=coreschema.Array()
+                )
+            ]
+
+        if not isinstance(serializer, serializers.Serializer):
+            return []
+
+        fields = []
+        for field in serializer.fields.values():
+            if field.read_only or isinstance(field, serializers.HiddenField):
+                continue
+
+            required = field.required and method != 'PATCH'
+            field = coreapi.Field(
+                name=field.field_name,
+                location='form',
+                required=required,
+                schema=field_to_schema(field)
+            )
+            fields.append(field)
+
+        return fields
+
+    def get_pagination_fields(self, path, method):
+        view = self.view
+
+        if not is_list_view(path, method, view):
+            return []
+
+        pagination = getattr(view, 'pagination_class', None)
+        if not pagination:
+            return []
+
+        paginator = view.pagination_class()
+        return paginator.get_schema_fields(view)
+
+    def _allows_filters(self, path, method):
+        """
+        Determine whether to include filter Fields in schema.
+
+        Default implementation looks for ModelViewSet or GenericAPIView
+        actions/methods that cause filtering on the default implementation.
+
+        Override to adjust behaviour for your view.
+
+        Note: Introduced in v3.7: Initially "private" (i.e. with leading underscore)
+            to allow changes based on user experience.
+        """
+        if getattr(self.view, 'filter_backends', None) is None:
+            return False
+
+        if hasattr(self.view, 'action'):
+            return self.view.action in ["list", "retrieve", "update", "partial_update", "destroy"]
+
+        return method.lower() in ["get", "put", "patch", "delete"]
+
+    def get_filter_fields(self, path, method):
+        if not self._allows_filters(path, method):
+            return []
+
+        fields = []
+        for filter_backend in self.view.filter_backends:
+            fields += filter_backend().get_schema_fields(self.view)
+        return fields
+
+    def get_manual_fields(self, path, method):
+        return self._manual_fields
+
+    @staticmethod
+    def update_fields(fields, update_with):
+        """
+        Update list of coreapi.Field instances, overwriting on `Field.name`.
+
+        Utility function to handle replacing coreapi.Field fields
+        from a list by name. Used to handle `manual_fields`.
+
+        Parameters:
+
+        * `fields`: list of `coreapi.Field` instances to update
+        * `update_with: list of `coreapi.Field` instances to add or replace.
+        """
+        if not update_with:
+            return fields
+
+        by_name = OrderedDict((f.name, f) for f in fields)
+        for f in update_with:
+            by_name[f.name] = f
+        fields = list(by_name.values())
+        return fields
+
+    def get_encoding(self, path, method):
+        """
+        Return the 'encoding' parameter to use for a given endpoint.
+        """
+        view = self.view
+
+        # Core API supports the following request encodings over HTTP...
+        supported_media_types = {
+            'application/json',
+            'application/x-www-form-urlencoded',
+            'multipart/form-data',
+        }
+        parser_classes = getattr(view, 'parser_classes', [])
+        for parser_class in parser_classes:
+            media_type = getattr(parser_class, 'media_type', None)
+            if media_type in supported_media_types:
+                return media_type
+            # Raw binary uploads are supported with "application/octet-stream"
+            if media_type == '*/*':
+                return 'application/octet-stream'
+
+        return None
+
+
+class ManualSchema(ViewInspector):
+    """
+    Allows providing a list of coreapi.Fields,
+    plus an optional description.
+    """
+    def __init__(self, fields, description='', encoding=None):
+        """
+        Parameters:
+
+        * `fields`: list of `coreapi.Field` instances.
+        * `description`: String description for view. Optional.
+        """
+        super(ManualSchema, self).__init__()
+        assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances"
+        self._fields = fields
+        self._description = description
+        self._encoding = encoding
+
+    def get_link(self, path, method, base_url):
+
+        if base_url and path.startswith('/'):
+            path = path[1:]
+
+        return coreapi.Link(
+            url=urlparse.urljoin(base_url, path),
+            action=method.lower(),
+            encoding=self._encoding,
+            fields=self._fields,
+            description=self._description
+        )
+
+
+class DefaultSchema(ViewInspector):
+    """Allows overriding AutoSchema using DEFAULT_SCHEMA_CLASS setting"""
+    def __get__(self, instance, owner):
+        result = super(DefaultSchema, self).__get__(instance, owner)
+        if not isinstance(result, DefaultSchema):
+            return result
+
+        inspector_class = api_settings.DEFAULT_SCHEMA_CLASS
+        assert issubclass(inspector_class, ViewInspector), (
+            "DEFAULT_SCHEMA_CLASS must be set to a ViewInspector (usually an AutoSchema) subclass"
+        )
+        inspector = inspector_class()
+        inspector.view = instance
+        return inspector

+ 24 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/utils.py

@@ -0,0 +1,24 @@
+"""
+utils.py        # Shared helper functions
+
+See schemas.__init__.py for package overview.
+"""
+from rest_framework.mixins import RetrieveModelMixin
+
+
+def is_list_view(path, method, view):
+    """
+    Return True if the given path/method appears to represent a list view.
+    """
+    if hasattr(view, 'action'):
+        # Viewsets have an explicitly defined action, which we can inspect.
+        return view.action == 'list'
+
+    if method.lower() != 'get':
+        return False
+    if isinstance(view, RetrieveModelMixin):
+        return False
+    path_components = path.strip('/').split('/')
+    if path_components and '{' in path_components[-1]:
+        return False
+    return True

+ 41 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/schemas/views.py

@@ -0,0 +1,41 @@
+"""
+views.py        # Houses `SchemaView`, `APIView` subclass.
+
+See schemas.__init__.py for package overview.
+"""
+from rest_framework import exceptions, renderers
+from rest_framework.response import Response
+from rest_framework.settings import api_settings
+from rest_framework.views import APIView
+
+
+class SchemaView(APIView):
+    _ignore_model_permissions = True
+    schema = None  # exclude from schema
+    renderer_classes = None
+    schema_generator = None
+    public = False
+
+    def __init__(self, *args, **kwargs):
+        super(SchemaView, self).__init__(*args, **kwargs)
+        if self.renderer_classes is None:
+            self.renderer_classes = [
+                renderers.OpenAPIRenderer,
+                renderers.CoreJSONRenderer
+            ]
+            if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
+                self.renderer_classes += [renderers.BrowsableAPIRenderer]
+
+    def get(self, request, *args, **kwargs):
+        schema = self.schema_generator.get_schema(request, self.public)
+        if schema is None:
+            raise exceptions.PermissionDenied()
+        return Response(schema)
+
+    def handle_exception(self, exc):
+        # Schema renderers do not render exceptions, so re-perform content
+        # negotiation with default renderers.
+        self.renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
+        neg = self.perform_content_negotiation(self.request, force=True)
+        self.request.accepted_renderer, self.request.accepted_media_type = neg
+        return super(SchemaView, self).handle_exception(exc)

+ 1618 - 0
desktop/core/ext-py/djangorestframework-3.9.4/rest_framework/serializers.py

@@ -0,0 +1,1618 @@
+"""
+Serializers and ModelSerializers are similar to Forms and ModelForms.
+Unlike forms, they are not constrained to dealing with HTML output, and
+form encoded input.
+
+Serialization in REST framework is a two-phase process:
+
+1. Serializers marshal between complex types like model instances, and
+python primitives.
+2. The process of marshalling between python primitives and request and
+response content is handled by parsers and renderers.
+"""
+from __future__ import unicode_literals
+
+import copy
+import inspect
+import traceback
+from collections import OrderedDict
+
+from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ValidationError as DjangoValidationError
+from django.db import models
+from django.db.models import DurationField as ModelDurationField
+from django.db.models.fields import Field as DjangoModelField
+from django.db.models.fields import FieldDoesNotExist
+from django.utils import six, timezone
+from django.utils.functional import cached_property
+from django.utils.translation import ugettext_lazy as _
+
+from rest_framework.compat import Mapping, postgres_fields, unicode_to_repr
+from rest_framework.exceptions import ErrorDetail, ValidationError
+from rest_framework.fields import get_error_detail, set_value
+from rest_framework.settings import api_settings
+from rest_framework.utils import html, model_meta, representation
+from rest_framework.utils.field_mapping import (
+    ClassLookupDict, get_field_kwargs, get_nested_relation_kwargs,
+    get_relation_kwargs, get_url_kwargs
+)
+from rest_framework.utils.serializer_helpers import (
+    BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict,
+    ReturnList
+)
+from rest_framework.validators import (
+    UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator,
+    UniqueTogetherValidator
+)
+
+# Note: We do the following so that users of the framework can use this style:
+#
+#     example_field = serializers.CharField(...)
+#
+# This helps keep the separation between model fields, form fields, and
+# serializer fields more explicit.
+from rest_framework.fields import (  # NOQA # isort:skip
+    BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
+    DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField,
+    HiddenField, HStoreField, IPAddressField, ImageField, IntegerField, JSONField,
+    ListField, ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField,
+    RegexField, SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
+)
+from rest_framework.relations import (  # NOQA # isort:skip
+    HyperlinkedIdentityField, HyperlinkedRelatedField, ManyRelatedField,
+    PrimaryKeyRelatedField, RelatedField, SlugRelatedField, StringRelatedField,
+)
+
+# Non-field imports, but public API
+from rest_framework.fields import (  # NOQA # isort:skip
+    CreateOnlyDefault, CurrentUserDefault, SkipField, empty
+)
+from rest_framework.relations import Hyperlink, PKOnlyObject  # NOQA # isort:skip
+
+# We assume that 'validators' are intended for the child serializer,
+# rather than the parent serializer.
+LIST_SERIALIZER_KWARGS = (
+    'read_only', 'write_only', 'required', 'default', 'initial', 'source',
+    'label', 'help_text', 'style', 'error_messages', 'allow_empty',
+    'instance', 'data', 'partial', 'context', 'allow_null'
+)
+
+ALL_FIELDS = '__all__'
+
+
+# BaseSerializer
+# --------------
+
+class BaseSerializer(Field):
+    """
+    The BaseSerializer class provides a minimal class which may be used
+    for writing custom serializer implementations.
+
+    Note that we strongly restrict the ordering of operations/properties
+    that may be used on the serializer in order to enforce correct usage.
+
+    In particular, if a `data=` argument is passed then:
+
+    .is_valid() - Available.
+    .initial_data - Available.
+    .validated_data - Only available after calling `is_valid()`
+    .errors - Only available after calling `is_valid()`
+    .data - Only available after calling `is_valid()`
+
+    If a `data=` argument is not passed then:
+
+    .is_valid() - Not available.
+    .initial_data - Not available.
+    .validated_data - Not available.
+    .errors - Not available.
+    .data - Available.
+    """
+
+    def __init__(self, instance=None, data=empty, **kwargs):
+        self.instance = instance
+        if data is not empty:
+            self.initial_data = data
+        self.partial = kwargs.pop('partial', False)
+        self._context = kwargs.pop('context', {})
+        kwargs.pop('many', None)
+        super(BaseSerializer, self).__init__(**kwargs)
+
+    def __new__(cls, *args, **kwargs):
+        # We override this method in order to automagically create
+        # `ListSerializer` classes instead when `many=True` is set.
+        if kwargs.pop('many', False):
+            return cls.many_init(*args, **kwargs)
+        return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
+
+    @classmethod
+    def many_init(cls, *args, **kwargs):
+        """
+        This method implements the creation of a `ListSerializer` parent
+        class when `many=True` is used. You can customize it if you need to
+        control which keyword arguments are passed to the parent, and
+        which are passed to the child.
+
+        Note that we're over-cautious in passing most arguments to both parent
+        and child classes in order to try to cover the general case. If you're
+        overriding this method you'll probably want something much simpler, eg:
+
+        @classmethod
+        def many_init(cls, *args, **kwargs):
+            kwargs['child'] = cls()
+            return CustomListSerializer(*args, **kwargs)
+        """
+        allow_empty = kwargs.pop('allow_empty', None)
+        child_serializer = cls(*args, **kwargs)
+        list_kwargs = {
+            'child': child_serializer,
+        }
+        if allow_empty is not None:
+            list_kwargs['allow_empty'] = allow_empty
+        list_kwargs.update({
+            key: value for key, value in kwargs.items()
+            if key in LIST_SERIALIZER_KWARGS
+        })
+        meta = getattr(cls, 'Meta', None)
+        list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
+        return list_serializer_class(*args, **list_kwargs)
+
+    def to_internal_value(self, data):
+        raise NotImplementedError('`to_internal_value()` must be implemented.')
+
+    def to_representation(self, instance):
+        raise NotImplementedError('`to_representation()` must be implemented.')
+
+    def update(self, instance, validated_data):
+        raise NotImplementedError('`update()` must be implemented.')
+
+    def create(self, validated_data):
+        raise NotImplementedError('`create()` must be implemented.')
+
+    def save(self, **kwargs):
+        assert not hasattr(self, 'save_object'), (
+            'Serializer `%s.%s` has old-style version 2 `.save_object()` '
+            'that is no longer compatible with REST framework 3. '
+            'Use the new-style `.create()` and `.update()` methods instead.' %
+            (self.__class__.__module__, self.__class__.__name__)
+        )
+
+        assert hasattr(self, '_errors'), (
+            'You must call `.is_valid()` before calling `.save()`.'
+        )
+
+        assert not self.errors, (
+            'You cannot call `.save()` on a serializer with invalid data.'
+        )
+
+        # Guard against incorrect use of `serializer.save(commit=False)`
+        assert 'commit' not in kwargs, (
+            "'commit' is not a valid keyword argument to the 'save()' method. "
+            "If you need to access data before committing to the database then "
+            "inspect 'serializer.validated_data' instead. "
+            "You can also pass additional keyword arguments to 'save()' if you "
+            "need to set extra attributes on the saved model instance. "
+            "For example: 'serializer.save(owner=request.user)'.'"
+        )
+
+        assert not hasattr(self, '_data'), (
+            "You cannot call `.save()` after accessing `serializer.data`."
+            "If you need to access data before committing to the database then "
+            "inspect 'serializer.validated_data' instead. "
+        )
+
+        validated_data = dict(
+            list(self.validated_data.items()) +
+            list(kwargs.items())
+        )
+
+        if self.instance is not None:
+            self.instance = self.update(self.instance, validated_data)
+            assert self.instance is not None, (
+                '`update()` did not return an object instance.'
+            )
+        else:
+            self.instance = self.create(validated_data)
+            assert self.instance is not None, (
+                '`create()` did not return an object instance.'
+            )
+
+        return self.instance
+
+    def is_valid(self, raise_exception=False):
+        assert not hasattr(self, 'restore_object'), (
+            'Serializer `%s.%s` has old-style version 2 `.restore_object()` '
+            'that is no longer compatible with REST framework 3. '
+            'Use the new-style `.create()` and `.update()` methods instead.' %
+            (self.__class__.__module__, self.__class__.__name__)
+        )
+
+        assert hasattr(self, 'initial_data'), (
+            'Cannot call `.is_valid()` as no `data=` keyword argument was '
+            'passed when instantiating the serializer instance.'
+        )
+
+        if not hasattr(self, '_validated_data'):
+            try:
+                self._validated_data = self.run_validation(self.initial_data)
+            except ValidationError as exc:
+                self._validated_data = {}
+                self._errors = exc.detail
+            else:
+                self._errors = {}
+
+        if self._errors and raise_exception:
+            raise ValidationError(self.errors)
+
+        return not bool(self._errors)
+
+    @property
+    def data(self):
+        if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
+            msg = (
+                'When a serializer is passed a `data` keyword argument you '
+                'must call `.is_valid()` before attempting to access the '
+                'serialized `.data` representation.\n'
+                'You should either call `.is_valid()` first, '
+                'or access `.initial_data` instead.'
+            )
+            raise AssertionError(msg)
+
+        if not hasattr(self, '_data'):
+            if self.instance is not None and not getattr(self, '_errors', None):
+                self._data = self.to_representation(self.instance)
+            elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
+                self._data = self.to_representation(self.validated_data)
+            else:
+                self._data = self.get_initial()
+        return self._data
+
+    @property
+    def errors(self):
+        if not hasattr(self, '_errors'):
+            msg = 'You must call `.is_valid()` before accessing `.errors`.'
+            raise AssertionError(msg)
+        return self._errors
+
+    @property
+    def validated_data(self):
+        if not hasattr(self, '_validated_data'):
+            msg = 'You must call `.is_valid()` before accessing `.validated_data`.'
+            raise AssertionError(msg)
+        return self._validated_data
+
+
+# Serializer & ListSerializer classes
+# -----------------------------------
+
+class SerializerMetaclass(type):
+    """
+    This metaclass sets a dictionary named `_declared_fields` on the class.
+
+    Any instances of `Field` included as attributes on either the class
+    or on any of its superclasses will be include in the
+    `_declared_fields` dictionary.
+    """
+
+    @classmethod
+    def _get_declared_fields(cls, bases, attrs):
+        fields = [(field_name, attrs.pop(field_name))
+                  for field_name, obj in list(attrs.items())
+                  if isinstance(obj, Field)]
+        fields.sort(key=lambda x: x[1]._creation_counter)
+
+        # If this class is subclassing another Serializer, add that Serializer's
+        # fields.  Note that we loop over the bases in *reverse*. This is necessary
+        # in order to maintain the correct order of fields.
+        for base in reversed(bases):
+            if hasattr(base, '_declared_fields'):
+                fields = [
+                    (field_name, obj) for field_name, obj
+                    in base._declared_fields.items()
+                    if field_name not in attrs
+                ] + fields
+
+        return OrderedDict(fields)
+
+    def __new__(cls, name, bases, attrs):
+        attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
+        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
+def as_serializer_error(exc):
+    assert isinstance(exc, (ValidationError, DjangoValidationError))
+
+    if isinstance(exc, DjangoValidationError):
+        detail = get_error_detail(exc)
+    else:
+        detail = exc.detail
+
+    if isinstance(detail, Mapping):
+        # If errors may be a dict we use the standard {key: list of values}.
+        # Here we ensure that all the values are *lists* of errors.
+        return {
+            key: value if isinstance(value, (list, Mapping)) else [value]
+            for key, value in detail.items()
+        }
+    elif isinstance(detail, list):
+        # Errors raised as a list are non-field errors.
+        return {
+            api_settings.NON_FIELD_ERRORS_KEY: detail
+        }
+    # Errors raised as a string are non-field errors.
+    return {
+        api_settings.NON_FIELD_ERRORS_KEY: [detail]
+    }
+
+
+@six.add_metaclass(SerializerMetaclass)
+class Serializer(BaseSerializer):
+    default_error_messages = {
+        'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
+    }
+
+    @property
+    def fields(self):
+        """
+        A dictionary of {field_name: field_instance}.
+        """
+        # `fields` is evaluated lazily. We do this to ensure that we don't
+        # have issues importing modules that use ModelSerializers as fields,
+        # even if Django's app-loading stage has not yet run.
+        if not hasattr(self, '_fields'):
+            self._fields = BindingDict(self)
+            for key, value in self.get_fields().items():
+                self._fields[key] = value
+        return self._fields
+
+    @cached_property
+    def _writable_fields(self):
+        return [
+            field for field in self.fields.values() if not field.read_only
+        ]
+
+    @cached_property
+    def _readable_fields(self):
+        return [
+            field for field in self.fields.values()
+            if not field.write_only
+        ]
+
+    def get_fields(self):
+        """
+        Returns a dictionary of {field_name: field_instance}.
+        """
+        # Every new serializer is created with a clone of the field instances.
+        # This allows users to dynamically modify the fields on a serializer
+        # instance without affecting every other serializer instance.
+        return copy.deepcopy(self._declared_fields)
+
+    def get_validators(self):
+        """
+        Returns a list of validator callables.
+        """
+        # Used by the lazily-evaluated `validators` property.
+        meta = getattr(self, 'Meta', None)
+        validators = getattr(meta, 'validators', None)
+        return list(validators) if validators else []
+
+    def get_initial(self):
+        if hasattr(self, 'initial_data'):
+            # initial_data may not be a valid type
+            if not isinstance(self.initial_data, Mapping):
+                return OrderedDict()
+
+            return OrderedDict([
+                (field_name, field.get_value(self.initial_data))
+                for field_name, field in self.fields.items()
+                if (field.get_value(self.initial_data) is not empty) and
+                not field.read_only
+            ])
+
+        return OrderedDict([
+            (field.field_name, field.get_initial())
+            for field in self.fields.values()
+            if not field.read_only
+        ])
+
+    def get_value(self, dictionary):
+        # We override the default field access in order to support
+        # nested HTML forms.
+        if html.is_html_input(dictionary):
+            return html.parse_html_dict(dictionary, prefix=self.field_name) or empty
+        return dictionary.get(self.field_name, empty)
+
+    def run_validation(self, data=empty):
+        """
+        We override the default `run_validation`, because the validation
+        performed by validators and the `.validate()` method should
+        be coerced into an error dictionary with a 'non_fields_error' key.
+        """
+        (is_empty_value, data) = self.validate_empty_values(data)
+        if is_empty_value:
+            return data
+
+        value = self.to_internal_value(data)
+        try:
+            self.run_validators(value)
+            value = self.validate(value)
+            assert value is not None, '.validate() should return the validated data'
+        except (ValidationError, DjangoValidationError) as exc:
+            raise ValidationError(detail=as_serializer_error(exc))
+
+        return value
+
+    def _read_only_defaults(self):
+        fields = [
+            field for field in self.fields.values()
+            if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source)
+        ]
+
+        defaults = OrderedDict()
+        for field in fields:
+            try:
+                default = field.get_default()
+            except SkipField:
+                continue
+            defaults[field.field_name] = default
+
+        return defaults
+
+    def run_validators(self, value):
+        """
+        Add read_only fields with defaults to value before running validators.
+        """
+        if isinstance(value, dict):
+            to_validate = self._read_only_defaults()
+            to_validate.update(value)
+        else:
+            to_validate = value
+        super(Serializer, self).run_validators(to_validate)
+
+    def to_internal_value(self, data):
+        """
+        Dict of native values <- Dict of primitive datatypes.
+        """
+        if not isinstance(data, Mapping):
+            message = self.error_messages['invalid'].format(
+                datatype=type(data).__name__
+            )
+            raise ValidationError({
+                api_settings.NON_FIELD_ERRORS_KEY: [message]
+            }, code='invalid')
+
+        ret = OrderedDict()
+        errors = OrderedDict()
+        fields = self._writable_fields
+
+        for field in fields:
+            validate_method = getattr(self, 'validate_' + field.field_name, None)
+            primitive_value = field.get_value(data)
+            try:
+                validated_value = field.run_validation(primitive_value)
+                if validate_method is not None:
+                    validated_value = validate_method(validated_value)
+            except ValidationError as exc:
+                errors[field.field_name] = exc.detail
+            except DjangoValidationError as exc:
+                errors[field.field_name] = get_error_detail(exc)
+            except SkipField:
+                pass
+            else:
+                set_value(ret, field.source_attrs, validated_value)
+
+        if errors:
+            raise ValidationError(errors)
+
+        return ret
+
+    def to_representation(self, instance):
+        """
+        Object instance -> Dict of primitive datatypes.
+        """
+        ret = OrderedDict()
+        fields = self._readable_fields
+
+        for field in fields:
+            try:
+                attribute = field.get_attribute(instance)
+            except SkipField:
+                continue
+
+            # We skip `to_representation` for `None` values so that fields do
+            # not have to explicitly deal with that case.
+            #
+            # For related fields with `use_pk_only_optimization` we need to
+            # resolve the pk value.
+            check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
+            if check_for_none is None:
+                ret[field.field_name] = None
+            else:
+                ret[field.field_name] = field.to_representation(attribute)
+
+        return ret
+
+    def validate(self, attrs):
+        return attrs
+
+    def __repr__(self):
+        return unicode_to_repr(representation.serializer_repr(self, indent=1))
+
+    # The following are used for accessing `BoundField` instances on the
+    # serializer, for the purposes of presenting a form-like API onto the
+    # field values and field errors.
+
+    def __iter__(self):
+        for field in self.fields.values():
+            yield self[field.field_name]
+
+    def __getitem__(self, key):
+        field = self.fields[key]
+        value = self.data.get(key)
+        error = self.errors.get(key) if hasattr(self, '_errors') else None
+        if isinstance(field, Serializer):
+            return NestedBoundField(field, value, error)
+        if isinstance(field, JSONField):
+            return JSONBoundField(field, value, error)
+        return BoundField(field, value, error)
+
+    # Include a backlink to the serializer class on return objects.
+    # Allows renderers such as HTMLFormRenderer to get the full field info.
+
+    @property
+    def data(self):
+        ret = super(Serializer, self).data
+        return ReturnDict(ret, serializer=self)
+
+    @property
+    def errors(self):
+        ret = super(Serializer, self).errors
+        if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null':
+            # Edge case. Provide a more descriptive error than
+            # "this field may not be null", when no data is passed.
+            detail = ErrorDetail('No data provided', code='null')
+            ret = {api_settings.NON_FIELD_ERRORS_KEY: [detail]}
+        return ReturnDict(ret, serializer=self)
+
+
+# There's some replication of `ListField` here,
+# but that's probably better than obfuscating the call hierarchy.
+
+class ListSerializer(BaseSerializer):
+    child = None
+    many = True
+
+    default_error_messages = {
+        'not_a_list': _('Expected a list of items but got type "{input_type}".'),
+        'empty': _('This list may not be empty.')
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.child = kwargs.pop('child', copy.deepcopy(self.child))
+        self.allow_empty = kwargs.pop('allow_empty', True)
+        assert self.child is not None, '`child` is a required argument.'
+        assert not inspect.isclass(self.child), '`child` has not been instantiated.'
+        super(ListSerializer, self).__init__(*args, **kwargs)
+        self.child.bind(field_name='', parent=self)
+
+    def bind(self, field_name, parent):
+        super(ListSerializer, self).bind(field_name, parent)
+        self.partial = self.parent.partial
+
+    def get_initial(self):
+        if hasattr(self, 'initial_data'):
+            return self.to_representation(self.initial_data)
+        return []
+
+    def get_value(self, dictionary):
+        """
+        Given the input dictionary, return the field value.
+        """
+        # We override the default field access in order to support
+        # lists in HTML forms.
+        if html.is_html_input(dictionary):
+            return html.parse_html_list(dictionary, prefix=self.field_name, default=empty)
+        return dictionary.get(self.field_name, empty)
+
+    def run_validation(self, data=empty):
+        """
+        We override the default `run_validation`, because the validation
+        performed by validators and the `.validate()` method should
+        be coerced into an error dictionary with a 'non_fields_error' key.
+        """
+        (is_empty_value, data) = self.validate_empty_values(data)
+        if is_empty_value:
+            return data
+
+        value = self.to_internal_value(data)
+        try:
+            self.run_validators(value)
+            value = self.validate(value)
+            assert value is not None, '.validate() should return the validated data'
+        except (ValidationError, DjangoValidationError) as exc:
+            raise ValidationError(detail=as_serializer_error(exc))
+
+        return value
+
+    def to_internal_value(self, data):
+        """
+        List of dicts of native values <- List of dicts of primitive datatypes.
+        """
+        if html.is_html_input(data):
+            data = html.parse_html_list(data, default=[])
+
+        if not isinstance(data, list):
+            message = self.error_messages['not_a_list'].format(
+                input_type=type(data).__name__
+            )
+            raise ValidationError({
+                api_settings.NON_FIELD_ERRORS_KEY: [message]
+            }, code='not_a_list')
+
+        if not self.allow_empty and len(data) == 0:
+            if self.parent and self.partial:
+                raise SkipField()
+
+            message = self.error_messages['empty']
+            raise ValidationError({
+                api_settings.NON_FIELD_ERRORS_KEY: [message]
+            }, code='empty')
+
+        ret = []
+        errors = []
+
+        for item in data:
+            try:
+                validated = self.child.run_validation(item)
+            except ValidationError as exc:
+                errors.append(exc.detail)
+            else:
+                ret.append(validated)
+                errors.append({})
+
+        if any(errors):
+            raise ValidationError(errors)
+
+        return ret
+
+    def to_representation(self, data):
+        """
+        List of object instances -> List of dicts of primitive datatypes.
+        """
+        # Dealing with nested relationships, data can be a Manager,
+        # so, first get a queryset from the Manager if needed
+        iterable = data.all() if isinstance(data, models.Manager) else data
+
+        return [
+            self.child.to_representation(item) for item in iterable
+        ]
+
+    def validate(self, attrs):
+        return attrs
+
+    def update(self, instance, validated_data):
+        raise NotImplementedError(
+            "Serializers with many=True do not support multiple update by "
+            "default, only multiple create. For updates it is unclear how to "
+            "deal with insertions and deletions. If you need to support "
+            "multiple update, use a `ListSerializer` class and override "
+            "`.update()` so you can specify the behavior exactly."
+        )
+
+    def create(self, validated_data):
+        return [
+            self.child.create(attrs) for attrs in validated_data
+        ]
+
+    def save(self, **kwargs):
+        """
+        Save and return a list of object instances.
+        """
+        # Guard against incorrect use of `serializer.save(commit=False)`
+        assert 'commit' not in kwargs, (
+            "'commit' is not a valid keyword argument to the 'save()' method. "
+            "If you need to access data before committing to the database then "
+            "inspect 'serializer.validated_data' instead. "
+            "You can also pass additional keyword arguments to 'save()' if you "
+            "need to set extra attributes on the saved model instance. "
+            "For example: 'serializer.save(owner=request.user)'.'"
+        )
+
+        validated_data = [
+            dict(list(attrs.items()) + list(kwargs.items()))
+            for attrs in self.validated_data
+        ]
+
+        if self.instance is not None:
+            self.instance = self.update(self.instance, validated_data)
+            assert self.instance is not None, (
+                '`update()` did not return an object instance.'
+            )
+        else:
+            self.instance = self.create(validated_data)
+            assert self.instance is not None, (
+                '`create()` did not return an object instance.'
+            )
+
+        return self.instance
+
+    def is_valid(self, raise_exception=False):
+        # This implementation is the same as the default,
+        # except that we use lists, rather than dicts, as the empty case.
+        assert hasattr(self, 'initial_data'), (
+            'Cannot call `.is_valid()` as no `data=` keyword argument was '
+            'passed when instantiating the serializer instance.'
+        )
+
+        if not hasattr(self, '_validated_data'):
+            try:
+                self._validated_data = self.run_validation(self.initial_data)
+            except ValidationError as exc:
+                self._validated_data = []
+                self._errors = exc.detail
+            else:
+                self._errors = []
+
+        if self._errors and raise_exception:
+            raise ValidationError(self.errors)
+
+        return not bool(self._errors)
+
+    def __repr__(self):
+        return unicode_to_repr(representation.list_repr(self, indent=1))
+
+    # Include a backlink to the serializer class on return objects.
+    # Allows renderers such as HTMLFormRenderer to get the full field info.
+
+    @property
+    def data(self):
+        ret = super(ListSerializer, self).data
+        return ReturnList(ret, serializer=self)
+
+    @property
+    def errors(self):
+        ret = super(ListSerializer, self).errors
+        if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null':
+            # Edge case. Provide a more descriptive error than
+            # "this field may not be null", when no data is passed.
+            detail = ErrorDetail('No data provided', code='null')
+            ret = {api_settings.NON_FIELD_ERRORS_KEY: [detail]}
+        if isinstance(ret, dict):
+            return ReturnDict(ret, serializer=self)
+        return ReturnList(ret, serializer=self)
+
+
+# ModelSerializer & HyperlinkedModelSerializer
+# --------------------------------------------
+
+def raise_errors_on_nested_writes(method_name, serializer, validated_data):
+    """
+    Give explicit errors when users attempt to pass writable nested data.
+
+    If we don't do this explicitly they'd get a less helpful error when
+    calling `.save()` on the serializer.
+
+    We don't *automatically* support these sorts of nested writes because
+    there are too many ambiguities to define a default behavior.
+
+    Eg. Suppose we have a `UserSerializer` with a nested profile. How should
+    we handle the case of an update, where the `profile` relationship does
+    not exist? Any of the following might be valid:
+
+    * Raise an application error.
+    * Silently ignore the nested part of the update.
+    * Automatically create a profile instance.
+    """
+
+    # Ensure we don't have a writable nested field. For example:
+    #
+    # class UserSerializer(ModelSerializer):
+    #     ...
+    #     profile = ProfileSerializer()
+    assert not any(
+        isinstance(field, BaseSerializer) and
+        (field.source in validated_data) and
+        isinstance(validated_data[field.source], (list, dict))
+        for field in serializer._writable_fields
+    ), (
+        'The `.{method_name}()` method does not support writable nested '
+        'fields by default.\nWrite an explicit `.{method_name}()` method for '
+        'serializer `{module}.{class_name}`, or set `read_only=True` on '
+        'nested serializer fields.'.format(
+            method_name=method_name,
+            module=serializer.__class__.__module__,
+            class_name=serializer.__class__.__name__
+        )
+    )
+
+    # Ensure we don't have a writable dotted-source field. For example:
+    #
+    # class UserSerializer(ModelSerializer):
+    #     ...
+    #     address = serializer.CharField('profile.address')
+    assert not any(
+        '.' in field.source and
+        (key in validated_data) and
+        isinstance(validated_data[key], (list, dict))
+        for key, field in serializer.fields.items()
+    ), (
+        'The `.{method_name}()` method does not support writable dotted-source '
+        'fields by default.\nWrite an explicit `.{method_name}()` method for '
+        'serializer `{module}.{class_name}`, or set `read_only=True` on '
+        'dotted-source serializer fields.'.format(
+            method_name=method_name,
+            module=serializer.__class__.__module__,
+            class_name=serializer.__class__.__name__
+        )
+    )
+
+
+class ModelSerializer(Serializer):
+    """
+    A `ModelSerializer` is just a regular `Serializer`, except that:
+
+    * A set of default fields are automatically populated.
+    * A set of default validators are automatically populated.
+    * Default `.create()` and `.update()` implementations are provided.
+
+    The process of automatically determining a set of serializer fields
+    based on the model fields is reasonably complex, but you almost certainly
+    don't need to dig into the implementation.
+
+    If the `ModelSerializer` class *doesn't* generate the set of fields that
+    you need you should either declare the extra/differing fields explicitly on
+    the serializer class, or simply use a `Serializer` class.
+    """
+    serializer_field_mapping = {
+        models.AutoField: IntegerField,
+        models.BigIntegerField: IntegerField,
+        models.BooleanField: BooleanField,
+        models.CharField: CharField,
+        models.CommaSeparatedIntegerField: CharField,
+        models.DateField: DateField,
+        models.DateTimeField: DateTimeField,
+        models.DecimalField: DecimalField,
+        models.EmailField: EmailField,
+        models.Field: ModelField,
+        models.FileField: FileField,
+        models.FloatField: FloatField,
+        models.ImageField: ImageField,
+        models.IntegerField: IntegerField,
+        models.NullBooleanField: NullBooleanField,
+        models.PositiveIntegerField: IntegerField,
+        models.PositiveSmallIntegerField: IntegerField,
+        models.SlugField: SlugField,
+        models.SmallIntegerField: IntegerField,
+        models.TextField: CharField,
+        models.TimeField: TimeField,
+        models.URLField: URLField,
+        models.GenericIPAddressField: IPAddressField,
+        models.FilePathField: FilePathField,
+    }
+    if ModelDurationField is not None:
+        serializer_field_mapping[ModelDurationField] = DurationField
+    serializer_related_field = PrimaryKeyRelatedField
+    serializer_related_to_field = SlugRelatedField
+    serializer_url_field = HyperlinkedIdentityField
+    serializer_choice_field = ChoiceField
+
+    # The field name for hyperlinked identity fields. Defaults to 'url'.
+    # You can modify this using the API setting.
+    #
+    # Note that if you instead need modify this on a per-serializer basis,
+    # you'll also need to ensure you update the `create` method on any generic
+    # views, to correctly handle the 'Location' response header for
+    # "HTTP 201 Created" responses.
+    url_field_name = None
+
+    # Default `create` and `update` behavior...
+    def create(self, validated_data):
+        """
+        We have a bit of extra checking around this in order to provide
+        descriptive messages when something goes wrong, but this method is
+        essentially just:
+
+            return ExampleModel.objects.create(**validated_data)
+
+        If there are many to many fields present on the instance then they
+        cannot be set until the model is instantiated, in which case the
+        implementation is like so:
+
+            example_relationship = validated_data.pop('example_relationship')
+            instance = ExampleModel.objects.create(**validated_data)
+            instance.example_relationship = example_relationship
+            return instance
+
+        The default implementation also does not handle nested relationships.
+        If you want to support writable nested relationships you'll need
+        to write an explicit `.create()` method.
+        """
+        raise_errors_on_nested_writes('create', self, validated_data)
+
+        ModelClass = self.Meta.model
+
+        # Remove many-to-many relationships from validated_data.
+        # They are not valid arguments to the default `.create()` method,
+        # as they require that the instance has already been saved.
+        info = model_meta.get_field_info(ModelClass)
+        many_to_many = {}
+        for field_name, relation_info in info.relations.items():
+            if relation_info.to_many and (field_name in validated_data):
+                many_to_many[field_name] = validated_data.pop(field_name)
+
+        try:
+            instance = ModelClass._default_manager.create(**validated_data)
+        except TypeError:
+            tb = traceback.format_exc()
+            msg = (
+                'Got a `TypeError` when calling `%s.%s.create()`. '
+                'This may be because you have a writable field on the '
+                'serializer class that is not a valid argument to '
+                '`%s.%s.create()`. You may need to make the field '
+                'read-only, or override the %s.create() method to handle '
+                'this correctly.\nOriginal exception was:\n %s' %
+                (
+                    ModelClass.__name__,
+                    ModelClass._default_manager.name,
+                    ModelClass.__name__,
+                    ModelClass._default_manager.name,
+                    self.__class__.__name__,
+                    tb
+                )
+            )
+            raise TypeError(msg)
+
+        # Save many-to-many relationships after the instance is created.
+        if many_to_many:
+            for field_name, value in many_to_many.items():
+                field = getattr(instance, field_name)
+                field.set(value)
+
+        return instance
+
+    def update(self, instance, validated_data):
+        raise_errors_on_nested_writes('update', self, validated_data)
+        info = model_meta.get_field_info(instance)
+
+        # Simply set each attribute on the instance, and then save it.
+        # Note that unlike `.create()` we don't need to treat many-to-many
+        # relationships as being a special case. During updates we already
+        # have an instance pk for the relationships to be associated with.
+        for attr, value in validated_data.items():
+            if attr in info.relations and info.relations[attr].to_many:
+                field = getattr(instance, attr)
+                field.set(value)
+            else:
+                setattr(instance, attr, value)
+        instance.save()
+
+        return instance
+
+    # Determine the fields to apply...
+
+    def get_fields(self):
+        """
+        Return the dict of field names -> field instances that should be
+        used for `self.fields` when instantiating the serializer.
+        """
+        if self.url_field_name is None:
+            self.url_field_name = api_settings.URL_FIELD_NAME
+
+        assert hasattr(self, 'Meta'), (
+            'Class {serializer_class} missing "Meta" attribute'.format(
+                serializer_class=self.__class__.__name__
+            )
+        )
+        assert hasattr(self.Meta, 'model'), (
+            'Class {serializer_class} missing "Meta.model" attribute'.format(
+                serializer_class=self.__class__.__name__
+            )
+        )
+        if model_meta.is_abstract_model(self.Meta.model):
+            raise ValueError(
+                'Cannot use ModelSerializer with Abstract Models.'
+            )
+
+        declared_fields = copy.deepcopy(self._declared_fields)
+        model = getattr(self.Meta, 'model')
+        depth = getattr(self.Meta, 'depth', 0)
+
+        if depth is not None:
+            assert depth >= 0, "'depth' may not be negative."
+            assert depth <= 10, "'depth' may not be greater than 10."
+
+        # Retrieve metadata about fields & relationships on the model class.
+        info = model_meta.get_field_info(model)
+        field_names = self.get_field_names(declared_fields, info)
+
+        # Determine any extra field arguments and hidden fields that
+        # should be included
+        extra_kwargs = self.get_extra_kwargs()
+        extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs(
+            field_names, declared_fields, extra_kwargs
+        )
+
+        # Determine the fields that should be included on the serializer.
+        fields = OrderedDict()
+
+        for field_name in field_names:
+            # If the field is explicitly declared on the class then use that.
+            if field_name in declared_fields:
+                fields[field_name] = declared_fields[field_name]
+                continue
+
+            extra_field_kwargs = extra_kwargs.get(field_name, {})
+            source = extra_field_kwargs.get('source', '*')
+            if source == '*':
+                source = field_name
+
+            # Determine the serializer field class and keyword arguments.
+            field_class, field_kwargs = self.build_field(
+                source, info, model, depth
+            )
+
+            # Include any kwargs defined in `Meta.extra_kwargs`
+            field_kwargs = self.include_extra_kwargs(
+                field_kwargs, extra_field_kwargs
+            )
+
+            # Create the serializer field.
+            fields[field_name] = field_class(**field_kwargs)
+
+        # Add in any hidden fields.
+        fields.update(hidden_fields)
+
+        return fields
+
+    # Methods for determining the set of field names to include...
+
+    def get_field_names(self, declared_fields, info):
+        """
+        Returns the list of all field names that should be created when
+        instantiating this serializer class. This is based on the default
+        set of fields, but also takes into account the `Meta.fields` or
+        `Meta.exclude` options if they have been specified.
+        """
+        fields = getattr(self.Meta, 'fields', None)
+        exclude = getattr(self.Meta, 'exclude', None)
+
+        if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
+            raise TypeError(
+                'The `fields` option must be a list or tuple or "__all__". '
+                'Got %s.' % type(fields).__name__
+            )
+
+        if exclude and not isinstance(exclude, (list, tuple)):
+            raise TypeError(
+                'The `exclude` option must be a list or tuple. Got %s.' %
+                type(exclude).__name__
+            )
+
+        assert not (fields and exclude), (
+            "Cannot set both 'fields' and 'exclude' options on "
+            "serializer {serializer_class}.".format(
+                serializer_class=self.__class__.__name__
+            )
+        )
+
+        assert not (fields is None and exclude is None), (
+            "Creating a ModelSerializer without either the 'fields' attribute "
+            "or the 'exclude' attribute has been deprecated since 3.3.0, "
+            "and is now disallowed. Add an explicit fields = '__all__' to the "
+            "{serializer_class} serializer.".format(
+                serializer_class=self.__class__.__name__
+            ),
+        )
+
+        if fields == ALL_FIELDS:
+            fields = None
+
+        if fields is not None:
+            # Ensure that all declared fields have also been included in the
+            # `Meta.fields` option.
+
+            # Do not require any fields that are declared in a parent class,
+            # in order to allow serializer subclasses to only include
+            # a subset of fields.
+            required_field_names = set(declared_fields)
+            for cls in self.__class__.__bases__:
+                required_field_names -= set(getattr(cls, '_declared_fields', []))
+
+            for field_name in required_field_names:
+                assert field_name in fields, (
+                    "The field '{field_name}' was declared on serializer "
+                    "{serializer_class}, but has not been included in the "
+                    "'fields' option.".format(
+                        field_name=field_name,
+                        serializer_class=self.__class__.__name__
+                    )
+                )
+            return fields
+
+        # Use the default set of field names if `Meta.fields` is not specified.
+        fields = self.get_default_field_names(declared_fields, info)
+
+        if exclude is not None:
+            # If `Meta.exclude` is included, then remove those fields.
+            for field_name in exclude:
+                assert field_name not in self._declared_fields, (
+                    "Cannot both declare the field '{field_name}' and include "
+                    "it in the {serializer_class} 'exclude' option. Remove the "
+                    "field or, if inherited from a parent serializer, disable "
+                    "with `{field_name} = None`."
+                    .format(
+                        field_name=field_name,
+                        serializer_class=self.__class__.__name__
+                    )
+                )
+
+                assert field_name in fields, (
+                    "The field '{field_name}' was included on serializer "
+                    "{serializer_class} in the 'exclude' option, but does "
+                    "not match any model field.".format(
+                        field_name=field_name,
+                        serializer_class=self.__class__.__name__
+                    )
+                )
+                fields.remove(field_name)
+
+        return fields
+
+    def get_default_field_names(self, declared_fields, model_info):
+        """
+        Return the default list of field names that will be used if the
+        `Meta.fields` option is not specified.
+        """
+        return (
+            [model_info.pk.name] +
+            list(declared_fields) +
+            list(model_info.fields) +
+            list(model_info.forward_relations)
+        )
+
+    # Methods for constructing serializer fields...
+
+    def build_field(self, field_name, info, model_class, nested_depth):
+        """
+        Return a two tuple of (cls, kwargs) to build a serializer field with.
+        """
+        if field_name in info.fields_and_pk:
+            model_field = info.fields_and_pk[field_name]
+            return self.build_standard_field(field_name, model_field)
+
+        elif field_name in info.relations:
+            relation_info = info.relations[field_name]
+            if not nested_depth:
+                return self.build_relational_field(field_name, relation_info)
+            else:
+                return self.build_nested_field(field_name, relation_info, nested_depth)
+
+        elif hasattr(model_class, field_name):
+            return self.build_property_field(field_name, model_class)
+
+        elif field_name == self.url_field_name:
+            return self.build_url_field(field_name, model_class)
+
+        return self.build_unknown_field(field_name, model_class)
+
+    def build_standard_field(self, field_name, model_field):
+        """
+        Create regular model fields.
+        """
+        field_mapping = ClassLookupDict(self.serializer_field_mapping)
+
+        field_class = field_mapping[model_field]
+        field_kwargs = get_field_kwargs(field_name, model_field)
+
+        # Special case to handle when a OneToOneField is also the primary key
+        if model_field.one_to_one and model_field.primary_key:
+            field_class = self.serializer_related_field
+            field_kwargs['queryset'] = model_field.related_model.objects
+
+        if 'choices' in field_kwargs:
+            # Fields with choices get coerced into `ChoiceField`
+            # instead of using their regular typed field.
+            field_class = self.serializer_choice_field
+            # Some model fields may introduce kwargs that would not be valid
+            # for the choice field. We need to strip these out.
+            # Eg. models.DecimalField(max_digits=3, decimal_places=1, choices=DECIMAL_CHOICES)
+            valid_kwargs = {
+                'read_only', 'write_only',
+                'required', 'default', 'initial', 'source',
+                'label', 'help_text', 'style',
+                'error_messages', 'validators', 'allow_null', 'allow_blank',
+                'choices'
+            }
+            for key in list(field_kwargs):
+                if key not in valid_kwargs:
+                    field_kwargs.pop(key)
+
+        if not issubclass(field_class, ModelField):
+            # `model_field` is only valid for the fallback case of
+            # `ModelField`, which is used when no other typed field
+            # matched to the model field.
+            field_kwargs.pop('model_field', None)
+
+        if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField):
+            # `allow_blank` is only valid for textual fields.
+            field_kwargs.pop('allow_blank', None)
+
+        if postgres_fields and isinstance(model_field, postgres_fields.ArrayField):
+            # Populate the `child` argument on `ListField` instances generated
+            # for the PostgreSQL specific `ArrayField`.
+            child_model_field = model_field.base_field
+            child_field_class, child_field_kwargs = self.build_standard_field(
+                'child', child_model_field
+            )
+            field_kwargs['child'] = child_field_class(**child_field_kwargs)
+
+        return field_class, field_kwargs
+
+    def build_relational_field(self, field_name, relation_info):
+        """
+        Create fields for forward and reverse relationships.
+        """
+        field_class = self.serializer_related_field
+        field_kwargs = get_relation_kwargs(field_name, relation_info)
+
+        to_field = field_kwargs.pop('to_field', None)
+        if to_field and not relation_info.reverse and not relation_info.related_model._meta.get_field(to_field).primary_key:
+            field_kwargs['slug_field'] = to_field
+            field_class = self.serializer_related_to_field
+
+        # `view_name` is only valid for hyperlinked relationships.
+        if not issubclass(field_class, HyperlinkedRelatedField):
+            field_kwargs.pop('view_name', None)
+
+        return field_class, field_kwargs
+
+    def build_nested_field(self, field_name, relation_info, nested_depth):
+        """
+        Create nested fields for forward and reverse relationships.
+        """
+        class NestedSerializer(ModelSerializer):
+            class Meta:
+                model = relation_info.related_model
+                depth = nested_depth - 1
+                fields = '__all__'
+
+        field_class = NestedSerializer
+        field_kwargs = get_nested_relation_kwargs(relation_info)
+
+        return field_class, field_kwargs
+
+    def build_property_field(self, field_name, model_class):
+        """
+        Create a read only field for model methods and properties.
+        """
+        field_class = ReadOnlyField
+        field_kwargs = {}
+
+        return field_class, field_kwargs
+
+    def build_url_field(self, field_name, model_class):
+        """
+        Create a field representing the object's own URL.
+        """
+        field_class = self.serializer_url_field
+        field_kwargs = get_url_kwargs(model_class)
+
+        return field_class, field_kwargs
+
+    def build_unknown_field(self, field_name, model_class):
+        """
+        Raise an error on any unknown fields.
+        """
+        raise ImproperlyConfigured(
+            'Field name `%s` is not valid for model `%s`.' %
+            (field_name, model_class.__name__)
+        )
+
+    def include_extra_kwargs(self, kwargs, extra_kwargs):
+        """
+        Include any 'extra_kwargs' that have been included for this field,
+        possibly removing any incompatible existing keyword arguments.
+        """
+        if extra_kwargs.get('read_only', False):
+            for attr in [
+                'required', 'default', 'allow_blank', 'allow_null',
+                'min_length', 'max_length', 'min_value', 'max_value',
+                'validators', 'queryset'
+            ]:
+                kwargs.pop(attr, None)
+
+        if extra_kwargs.get('default') and kwargs.get('required') is False:
+            kwargs.pop('required')
+
+        if extra_kwargs.get('read_only', kwargs.get('read_only', False)):
+            extra_kwargs.pop('required', None)  # Read only fields should always omit the 'required' argument.
+
+        kwargs.update(extra_kwargs)
+
+        return kwargs
+
+    # Methods for determining additional keyword arguments to apply...
+
+    def get_extra_kwargs(self):
+        """
+        Return a dictionary mapping field names to a dictionary of
+        additional keyword arguments.
+        """
+        extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
+
+        read_only_fields = getattr(self.Meta, 'read_only_fields', None)
+        if read_only_fields is not None:
+            if not isinstance(read_only_fields, (list, tuple)):
+                raise TypeError(
+                    'The `read_only_fields` option must be a list or tuple. '
+                    'Got %s.' % type(read_only_fields).__name__
+                )
+            for field_name in read_only_fields:
+                kwargs = extra_kwargs.get(field_name, {})
+                kwargs['read_only'] = True
+                extra_kwargs[field_name] = kwargs
+
+        else:
+            # Guard against the possible misspelling `readonly_fields` (used
+            # by the Django admin and others).
+            assert not hasattr(self.Meta, 'readonly_fields'), (
+                'Serializer `%s.%s` has field `readonly_fields`; '
+                'the correct spelling for the option is `read_only_fields`.' %
+                (self.__class__.__module__, self.__class__.__name__)
+            )
+
+        return extra_kwargs
+
+    def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
+        """
+        Return any additional field options that need to be included as a
+        result of uniqueness constraints on the model. This is returned as
+        a two-tuple of:
+
+        ('dict of updated extra kwargs', 'mapping of hidden fields')
+        """
+        if getattr(self.Meta, 'validators', None) is not None:
+            return (extra_kwargs, {})
+
+        model = getattr(self.Meta, 'model')
+        model_fields = self._get_model_fields(
+            field_names, declared_fields, extra_kwargs
+        )
+
+        # Determine if we need any additional `HiddenField` or extra keyword
+        # arguments to deal with `unique_for` dates that are required to
+        # be in the input data in order to validate it.
+        unique_constraint_names = set()
+
+        for model_field in model_fields.values():
+            # Include each of the `unique_for_*` field names.
+            unique_constraint_names |= {model_field.unique_for_date, model_field.unique_for_month,
+                                        model_field.unique_for_year}
+
+        unique_constraint_names -= {None}
+
+        # Include each of the `unique_together` field names,
+        # so long as all the field names are included on the serializer.
+        for parent_class in [model] + list(model._meta.parents):
+            for unique_together_list in parent_class._meta.unique_together:
+                if set(field_names).issuperset(set(unique_together_list)):
+                    unique_constraint_names |= set(unique_together_list)
+
+        # Now we have all the field names that have uniqueness constraints
+        # applied, we can add the extra 'required=...' or 'default=...'
+        # arguments that are appropriate to these fields, or add a `HiddenField` for it.
+        hidden_fields = {}
+        uniqueness_extra_kwargs = {}
+
+        for unique_constraint_name in unique_constraint_names:
+            # Get the model field that is referred too.
+            unique_constraint_field = model._meta.get_field(unique_constraint_name)
+
+            if getattr(unique_constraint_field, 'auto_now_add', None):
+                default = CreateOnlyDefault(timezone.now)
+            elif getattr(unique_constraint_field, 'auto_now', None):
+                default = timezone.now
+            elif unique_constraint_field.has_default():
+                default = unique_constraint_field.default
+            else:
+                default = empty
+
+            if unique_constraint_name in model_fields:
+                # The corresponding field is present in the serializer
+                if default is empty:
+                    uniqueness_extra_kwargs[unique_constraint_name] = {'required': True}
+                else:
+                    uniqueness_extra_kwargs[unique_constraint_name] = {'default': default}
+            elif default is not empty:
+                # The corresponding field is not present in the
+                # serializer. We have a default to use for it, so
+                # add in a hidden field that populates it.
+                hidden_fields[unique_constraint_name] = HiddenField(default=default)
+
+        # Update `extra_kwargs` with any new options.
+        for key, value in uniqueness_extra_kwargs.items():
+            if key in extra_kwargs:
+                value.update(extra_kwargs[key])
+            extra_kwargs[key] = value
+
+        return extra_kwargs, hidden_fields
+
+    def _get_model_fields(self, field_names, declared_fields, extra_kwargs):
+        """
+        Returns all the model fields that are being mapped to by fields
+        on the serializer class.
+        Returned as a dict of 'model field name' -> 'model field'.
+        Used internally by `get_uniqueness_field_options`.
+        """
+        model = getattr(self.Meta, 'model')
+        model_fields = {}
+
+        for field_name in field_names:
+            if field_name in declared_fields:
+                # If the field is declared on the serializer
+                field = declared_fields[field_name]
+                source = field.source or field_name
+            else:
+                try:
+                    source = extra_kwargs[field_name]['source']
+                except KeyError:
+                    source = field_name
+
+            if '.' in source or source == '*':
+                # Model fields will always have a simple source mapping,
+                # they can't be nested attribute lookups.
+                continue
+
+            try:
+                field = model._meta.get_field(source)
+                if isinstance(field, DjangoModelField):
+                    model_fields[source] = field
+            except FieldDoesNotExist:
+                pass
+
+        return model_fields
+
+    # Determine the validators to apply...
+
+    def get_validators(self):
+        """
+        Determine the set of validators to use when instantiating serializer.
+        """
+        # If the validators have been declared explicitly then use that.
+        validators = getattr(getattr(self, 'Meta', None), 'validators', None)
+        if validators is not None:
+            return list(validators)
+
+        # Otherwise use the default set of validators.
+        return (
+            self.get_unique_together_validators() +
+            self.get_unique_for_date_validators()
+        )
+
+    def get_unique_together_validators(self):
+        """
+        Determine a default set of validators for any unique_together constraints.
+        """
+        model_class_inheritance_tree = (
+            [self.Meta.model] +
+            list(self.Meta.model._meta.parents)
+        )
+
+        # The field names we're passing though here only include fields
+        # which may map onto a model field. Any dotted field name lookups
+        # cannot map to a field, and must be a traversal, so we're not
+        # including those.
+        field_names = {
+            field.source for field in self._writable_fields
+            if (field.source != '*') and ('.' not in field.source)
+        }
+
+        # Special Case: Add read_only fields with defaults.
+        field_names |= {
+            field.source for field in self.fields.values()
+            if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source)
+        }
+
+        # Note that we make sure to check `unique_together` both on the
+        # base model class, but also on any parent classes.
+        validators = []
+        for parent_class in model_class_inheritance_tree:
+            for unique_together in parent_class._meta.unique_together:
+                if field_names.issuperset(set(unique_together)):
+                    validator = UniqueTogetherValidator(
+                        queryset=parent_class._default_manager,
+                        fields=unique_together
+                    )
+                    validators.append(validator)
+        return validators
+
+    def get_unique_for_date_validators(self):
+        """
+        Determine a default set of validators for the following constraints:
+
+        * unique_for_date
+        * unique_for_month
+        * unique_for_year
+        """
+        info = model_meta.get_field_info(self.Meta.model)
+        default_manager = self.Meta.model._default_manager
+        field_names = [field.source for field in self.fields.values()]
+
+        validators = []
+
+        for field_name, field in info.fields_and_pk.items():
+            if field.unique_for_date and field_name in field_names:
+                validator = UniqueForDateValidator(
+                    queryset=default_manager,
+                    field=field_name,
+                    date_field=field.unique_for_date
+                )
+                validators.append(validator)
+
+            if field.unique_for_month and field_name in field_names:
+                validator = UniqueForMonthValidator(
+                    queryset=default_manager,
+                    field=field_name,
+                    date_field=field.unique_for_month
+                )
+                validators.append(validator)
+
+            if field.unique_for_year and field_name in field_names:
+                validator = UniqueForYearValidator(
+                    queryset=default_manager,
+                    field=field_name,
+                    date_field=field.unique_for_year
+                )
+                validators.append(validator)
+
+        return validators
+
+
+if hasattr(models, 'UUIDField'):
+    ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField
+
+# IPAddressField is deprecated in Django
+if hasattr(models, 'IPAddressField'):
+    ModelSerializer.serializer_field_mapping[models.IPAddressField] = IPAddressField
+
+if postgres_fields:
+    ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = HStoreField
+    ModelSerializer.serializer_field_mapping[postgres_fields.ArrayField] = ListField
+    ModelSerializer.serializer_field_mapping[postgres_fields.JSONField] = JSONField
+
+
+class HyperlinkedModelSerializer(ModelSerializer):
+    """
+    A type of `ModelSerializer` that uses hyperlinked relationships instead
+    of primary key relationships. Specifically:
+
+    * A 'url' field is included instead of the 'id' field.
+    * Relationships to other instances are hyperlinks, instead of primary keys.
+    """
+    serializer_related_field = HyperlinkedRelatedField
+
+    def get_default_field_names(self, declared_fields, model_info):
+        """
+        Return the default list of field names that will be used if the
+        `Meta.fields` option is not specified.
+        """
+        return (
+            [self.url_field_name] +
+            list(declared_fields) +
+            list(model_info.fields) +
+            list(model_info.forward_relations)
+        )
+
+    def build_nested_field(self, field_name, relation_info, nested_depth):
+        """
+        Create nested fields for forward and reverse relationships.
+        """
+        class NestedSerializer(HyperlinkedModelSerializer):
+            class Meta:
+                model = relation_info.related_model
+                depth = nested_depth - 1
+                fields = '__all__'
+
+        field_class = NestedSerializer
+        field_kwargs = get_nested_relation_kwargs(relation_info)
+
+        return field_class, field_kwargs

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно