├── utils
├── __init__.py
├── urls.py
├── json.py
├── humanize_datetime.py
├── breadcrumbs.py
├── html.py
├── formatting.py
├── encoders.py
├── mediatypes.py
├── representation.py
├── serializer_helpers.py
└── model_meta.py
├── templatetags
└── __init__.py
├── authtoken
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── drf_create_token.py
├── migrations
│ ├── __init__.py
│ ├── 0001_initial.py
│ └── 0002_auto_20160226_1747.py
├── __init__.py
├── apps.py
├── admin.py
├── serializers.py
├── models.py
└── views.py
├── models.py
├── templates
└── rest_framework
│ ├── inline
│ ├── list_fieldset.html
│ ├── form.html
│ ├── fieldset.html
│ ├── list_field.html
│ ├── dict_field.html
│ ├── checkbox.html
│ ├── textarea.html
│ ├── input.html
│ ├── checkbox_multiple.html
│ ├── radio.html
│ ├── select.html
│ └── select_multiple.html
│ ├── api.html
│ ├── login.html
│ ├── admin
│ ├── simple_list_value.html
│ ├── list_value.html
│ ├── dict_value.html
│ ├── detail.html
│ └── list.html
│ ├── schema.js
│ ├── horizontal
│ ├── form.html
│ ├── list_field.html
│ ├── dict_field.html
│ ├── list_fieldset.html
│ ├── fieldset.html
│ ├── checkbox.html
│ ├── textarea.html
│ ├── input.html
│ ├── checkbox_multiple.html
│ ├── select.html
│ ├── select_multiple.html
│ └── radio.html
│ ├── vertical
│ ├── form.html
│ ├── list_field.html
│ ├── list_fieldset.html
│ ├── dict_field.html
│ ├── fieldset.html
│ ├── checkbox.html
│ ├── textarea.html
│ ├── input.html
│ ├── select.html
│ ├── checkbox_multiple.html
│ ├── select_multiple.html
│ └── radio.html
│ ├── docs
│ ├── langs
│ │ ├── shell-intro.html
│ │ ├── python-intro.html
│ │ ├── javascript-intro.html
│ │ ├── shell.html
│ │ ├── python.html
│ │ └── javascript.html
│ ├── document.html
│ ├── auth
│ │ ├── session.html
│ │ ├── basic.html
│ │ └── token.html
│ ├── interact.html
│ ├── error.html
│ ├── index.html
│ ├── sidebar.html
│ └── link.html
│ ├── raw_data_form.html
│ ├── filters
│ ├── search.html
│ ├── ordering.html
│ └── base.html
│ ├── pagination
│ ├── previous_and_next.html
│ └── numbers.html
│ └── login_base.html
├── locale
├── ar
│ └── LC_MESSAGES
│ │ └── django.mo
├── be
│ └── LC_MESSAGES
│ │ └── django.mo
├── ca
│ └── LC_MESSAGES
│ │ └── django.mo
├── cs
│ └── LC_MESSAGES
│ │ └── django.mo
├── da
│ └── LC_MESSAGES
│ │ └── django.mo
├── de
│ └── LC_MESSAGES
│ │ └── django.mo
├── el
│ └── LC_MESSAGES
│ │ └── django.mo
├── en
│ └── LC_MESSAGES
│ │ └── django.mo
├── es
│ └── LC_MESSAGES
│ │ └── django.mo
├── et
│ └── LC_MESSAGES
│ │ └── django.mo
├── fa
│ └── LC_MESSAGES
│ │ └── django.mo
├── fi
│ └── LC_MESSAGES
│ │ └── django.mo
├── fr
│ └── LC_MESSAGES
│ │ └── django.mo
├── gl
│ └── LC_MESSAGES
│ │ └── django.mo
├── hu
│ └── LC_MESSAGES
│ │ └── django.mo
├── id
│ └── LC_MESSAGES
│ │ └── django.mo
├── it
│ └── LC_MESSAGES
│ │ └── django.mo
├── ja
│ └── LC_MESSAGES
│ │ └── django.mo
├── lv
│ └── LC_MESSAGES
│ │ └── django.mo
├── mk
│ └── LC_MESSAGES
│ │ └── django.mo
├── nb
│ └── LC_MESSAGES
│ │ └── django.mo
├── nl
│ └── LC_MESSAGES
│ │ └── django.mo
├── nn
│ └── LC_MESSAGES
│ │ └── django.mo
├── no
│ └── LC_MESSAGES
│ │ └── django.mo
├── pl
│ └── LC_MESSAGES
│ │ └── django.mo
├── pt
│ └── LC_MESSAGES
│ │ └── django.mo
├── ro
│ └── LC_MESSAGES
│ │ └── django.mo
├── ru
│ └── LC_MESSAGES
│ │ └── django.mo
├── sk
│ └── LC_MESSAGES
│ │ └── django.mo
├── sl
│ └── LC_MESSAGES
│ │ └── django.mo
├── sv
│ └── LC_MESSAGES
│ │ └── django.mo
├── tr
│ └── LC_MESSAGES
│ │ └── django.mo
├── uk
│ └── LC_MESSAGES
│ │ └── django.mo
├── vi
│ └── LC_MESSAGES
│ │ └── django.mo
├── ach
│ └── LC_MESSAGES
│ │ └── django.mo
├── ca_ES
│ └── LC_MESSAGES
│ │ └── django.mo
├── el_GR
│ └── LC_MESSAGES
│ │ └── django.mo
├── en_AU
│ └── LC_MESSAGES
│ │ └── django.mo
├── en_CA
│ └── LC_MESSAGES
│ │ └── django.mo
├── en_US
│ └── LC_MESSAGES
│ │ └── django.mo
├── fa_IR
│ └── LC_MESSAGES
│ │ └── django.mo
├── fr_CA
│ └── LC_MESSAGES
│ │ └── django.mo
├── gl_ES
│ └── LC_MESSAGES
│ │ └── django.mo
├── he_IL
│ └── LC_MESSAGES
│ │ └── django.mo
├── ko_KR
│ └── LC_MESSAGES
│ │ └── django.mo
├── pt_BR
│ └── LC_MESSAGES
│ │ └── django.mo
├── pt_PT
│ └── LC_MESSAGES
│ │ └── django.mo
├── tr_TR
│ └── LC_MESSAGES
│ │ └── django.mo
├── zh_CN
│ └── LC_MESSAGES
│ │ └── django.mo
├── zh_TW
│ └── LC_MESSAGES
│ │ └── django.mo
├── zh_Hans
│ └── LC_MESSAGES
│ │ └── django.mo
└── zh_Hant
│ └── LC_MESSAGES
│ └── django.mo
├── static
└── rest_framework
│ ├── img
│ ├── grid.png
│ ├── glyphicons-halflings.png
│ └── glyphicons-halflings-white.png
│ ├── docs
│ ├── img
│ │ ├── grid.png
│ │ └── favicon.ico
│ ├── css
│ │ ├── jquery.json-view.min.css
│ │ └── highlight.css
│ └── js
│ │ └── jquery.json-view.min.js
│ ├── fonts
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
│ ├── css
│ ├── prettify.css
│ ├── default.css
│ └── bootstrap-tweaks.css
│ └── js
│ ├── default.js
│ ├── csrf.js
│ └── ajax-form.js
├── apps.py
├── schemas
├── utils.py
├── views.py
└── __init__.py
├── __init__.py
├── checks.py
├── urls.py
├── README.md
├── reverse.py
├── status.py
├── documentation.py
├── mixins.py
├── response.py
├── negotiation.py
├── urlpatterns.py
├── metadata.py
├── decorators.py
├── viewsets.py
├── permissions.py
└── versioning.py
/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/authtoken/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/authtoken/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/authtoken/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/models.py:
--------------------------------------------------------------------------------
1 | # Just to keep things like ./manage.py test happy
2 |
--------------------------------------------------------------------------------
/authtoken/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'
2 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/list_fieldset.html:
--------------------------------------------------------------------------------
1 | Lists are not currently supported in HTML input.
2 |
--------------------------------------------------------------------------------
/locale/ar/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ar/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/be/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/be/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ca/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ca/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/cs/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/cs/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/da/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/da/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/de/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/de/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/el/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/el/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/en/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/en/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/es/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/es/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/et/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/et/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/fa/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/fa/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/fi/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/fi/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/fr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/fr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/gl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/gl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/hu/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/hu/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/id/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/id/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/it/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/it/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ja/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ja/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/lv/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/lv/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/mk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/mk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/nb/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/nb/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/nl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/nl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/nn/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/nn/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/no/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/no/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/pl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/pl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/pt/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/pt/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ro/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ro/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ru/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/sk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/sk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/sl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/sl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/sv/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/sv/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/tr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/tr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/uk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/uk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/vi/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/vi/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ach/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ach/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ca_ES/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ca_ES/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/el_GR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/el_GR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/en_AU/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/en_AU/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/en_CA/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/en_CA/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/en_US/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/en_US/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/fa_IR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/fa_IR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/fr_CA/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/fr_CA/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/gl_ES/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/gl_ES/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/he_IL/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/he_IL/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/ko_KR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/ko_KR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/pt_BR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/pt_BR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/pt_PT/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/pt_PT/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/tr_TR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/tr_TR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/zh_CN/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/zh_CN/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/zh_TW/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/zh_TW/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/static/rest_framework/img/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/img/grid.png
--------------------------------------------------------------------------------
/locale/zh_Hans/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/zh_Hans/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/locale/zh_Hant/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/locale/zh_Hant/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/static/rest_framework/docs/img/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/docs/img/grid.png
--------------------------------------------------------------------------------
/templates/rest_framework/api.html:
--------------------------------------------------------------------------------
1 | {% extends "rest_framework/base.html" %}
2 |
3 | {# Override this template in your own templates directory to customize #}
4 |
--------------------------------------------------------------------------------
/static/rest_framework/docs/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/docs/img/favicon.ico
--------------------------------------------------------------------------------
/templates/rest_framework/login.html:
--------------------------------------------------------------------------------
1 | {% extends "rest_framework/login_base.html" %}
2 |
3 | {# Override this template in your own templates directory to customize #}
4 |
--------------------------------------------------------------------------------
/templates/rest_framework/admin/simple_list_value.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% for item in value %}{% if not forloop.first%},{% endif %} {{item|format_value}}{% endfor %}
3 |
--------------------------------------------------------------------------------
/templates/rest_framework/schema.js:
--------------------------------------------------------------------------------
1 | var codec = new window.coreapi.codecs.CoreJSONCodec()
2 | var coreJSON = window.atob('{{ schema }}')
3 | window.schema = codec.decode(coreJSON)
4 |
--------------------------------------------------------------------------------
/static/rest_framework/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/static/rest_framework/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/static/rest_framework/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/static/rest_framework/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/static/rest_framework/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/static/rest_framework/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/static/rest_framework/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/static/rest_framework/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/static/rest_framework/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/HEAD/static/rest_framework/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% for field in form %}
3 | {% if not field.read_only %}
4 | {% render_field field style=style %}
5 | {% endif %}
6 | {% endfor %}
7 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% for field in form %}
3 | {% if not field.read_only %}
4 | {% render_field field style=style %}
5 | {% endif %}
6 | {% endfor %}
7 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% for field in form %}
3 | {% if not field.read_only %}
4 | {% render_field field style=style %}
5 | {% endif %}
6 | {% endfor %}
7 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% for nested_field in field %}
3 | {% if not nested_field.read_only %}
4 | {% render_field nested_field style=style %}
5 | {% endif %}
6 | {% endfor %}
7 |
--------------------------------------------------------------------------------
/authtoken/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 |
5 | class AuthTokenConfig(AppConfig):
6 | name = 'rest_framework.authtoken'
7 | verbose_name = _("Auth Token")
8 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/langs/shell-intro.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
{% code bash %}# Install the command line client
3 | $ pip install coreapi-cli{% endcode %}
4 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/langs/python-intro.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% code bash %}# Install the Python client library
3 | $ pip install coreapi{% endcode %}
4 |
--------------------------------------------------------------------------------
/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class RestFrameworkConfig(AppConfig):
5 | name = 'rest_framework'
6 | verbose_name = "Django REST framework"
7 |
8 | def ready(self):
9 | # Add System checks
10 | from .checks import pagination_system_check # NOQA
11 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/list_field.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/dict_field.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/list_field.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/list_fieldset.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {{ field.label }}
5 |
6 | {% endif %}
7 |
8 | Lists are not currently supported in HTML input.
9 |
10 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/dict_field.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/authtoken/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from rest_framework.authtoken.models import Token
4 |
5 |
6 | class TokenAdmin(admin.ModelAdmin):
7 | list_display = ('key', 'user', 'created')
8 | fields = ('user',)
9 | ordering = ('-created',)
10 |
11 |
12 | admin.site.register(Token, TokenAdmin)
13 |
--------------------------------------------------------------------------------
/templates/rest_framework/admin/list_value.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% for item in value %}
5 |
6 | {{ forloop.counter0 }}
7 | {{ item|format_value }}
8 |
9 | {% endfor %}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templates/rest_framework/admin/dict_value.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% for k, v in value|items %}
5 |
6 | {{ k|format_value }}
7 | {{ v|format_value }}
8 |
9 | {% endfor %}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/checkbox.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/templates/rest_framework/admin/detail.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% for key, value in results|items %}
5 | {% if key in details %}
6 | {{ key|capfirst }} {{ value|format_value }}
7 | {% endif %}
8 | {% endfor %}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/langs/javascript-intro.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% load static %}
3 | {% code html %}
4 |
5 | {% endcode %}
6 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/list_field.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/dict_field.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/templates/rest_framework/raw_data_form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {{ form.non_field_errors }}
3 | {% for field in form %}
4 |
11 | {% endfor %}
12 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/textarea.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {{ field.label }}
5 |
6 | {% endif %}
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% if field.label %}
5 |
6 | {{ field.label }}
7 |
8 | {% endif %}
9 |
10 | {% for nested_field in field %}
11 | {% if not nested_field.read_only %}
12 | {% render_field nested_field style=style %}
13 | {% endif %}
14 | {% endfor %}
15 |
16 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/langs/shell.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% code bash %}# Load the schema document
3 | $ coreapi get {{ document.url }}{% if schema_format %} --format {{ schema_format }}{% endif %}
4 |
5 | # Interact with the API endpoint
6 | $ coreapi action {% if section_key %}{{ section_key }} {% endif %}{{ link_key }}{% for field in link.fields %} -p {{ field.name }}=...{% endfor %}{% endcode %}
7 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/list_fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% if field.label %}
5 |
6 |
7 | {{ field.label }}
8 |
9 |
10 | {% endif %}
11 |
12 | Lists are not currently supported in HTML input.
13 |
14 |
--------------------------------------------------------------------------------
/templates/rest_framework/filters/search.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "Search" %}
3 |
13 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 | {% if field.label %}
4 |
5 |
6 | {{ field.label }}
7 |
8 |
9 | {% endif %}
10 |
11 | {% for nested_field in field %}
12 | {% if not nested_field.read_only %}
13 | {% render_field nested_field style=style %}
14 | {% endif %}
15 | {% endfor %}
16 |
17 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/input.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {{ field.label }}
5 |
6 | {% endif %}
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/checkbox_multiple.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
17 |
--------------------------------------------------------------------------------
/templates/rest_framework/pagination/previous_and_next.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/templates/rest_framework/filters/ordering.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% load i18n %}
3 | {% trans "Ordering" %}
4 |
5 | {% for key, label in options %}
6 | {% if key == current %}
7 |
8 | {{ label }}
9 |
10 | {% else %}
11 |
{{ label }}
12 | {% endif %}
13 | {% endfor %}
14 |
15 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/checkbox.html:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/templates/rest_framework/filters/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 | {% for element in elements %}
10 | {% if not forloop.first %}
{% endif %}
11 | {{ element }}
12 | {% endfor %}
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/templates/rest_framework/admin/list.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% for column in columns%}{{ column|capfirst }} {% endfor %}
5 |
6 |
7 | {% for row in results %}
8 |
9 | {% for key, value in row|items %}
10 | {% if key in columns %}
11 |
12 | {{ value|format_value }}
13 |
14 | {% endif %}
15 | {% endfor %}
16 |
17 |
18 |
19 |
20 | {% endfor %}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/langs/python.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% code python %}import coreapi
3 |
4 | # Initialize a client & load the schema document
5 | client = coreapi.Client()
6 | schema = client.get("{{ document.url }}"{% if schema_format %}, format="{{ schema_format }}"{% endif %})
7 |
8 | # Interact with the API endpoint
9 | action = [{% if section_key %}"{{ section_key }}", {% endif %}"{{ link_key }}"]
10 | {% if link.fields %}params = {
11 | {% for field in link.fields %} "{{ field.name }}": ...{% if not loop.last %},{% endif %}
12 | {% endfor %}}
13 | {% endif %}result = client.action(schema, action{% if link.fields %}, params=params{% endif %}){% endcode %}
14 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/checkbox.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/schemas/utils.py:
--------------------------------------------------------------------------------
1 | """
2 | utils.py # Shared helper functions
3 |
4 | See schemas.__init__.py for package overview.
5 | """
6 | from rest_framework.mixins import RetrieveModelMixin
7 |
8 |
9 | def is_list_view(path, method, view):
10 | """
11 | Return True if the given path/method appears to represent a list view.
12 | """
13 | if hasattr(view, 'action'):
14 | # Viewsets have an explicitly defined action, which we can inspect.
15 | return view.action == 'list'
16 |
17 | if method.lower() != 'get':
18 | return False
19 | if isinstance(view, RetrieveModelMixin):
20 | return False
21 | path_components = path.strip('/').split('/')
22 | if path_components and '{' in path_components[-1]:
23 | return False
24 | return True
25 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/textarea.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {{ field.label }}
5 |
6 | {% endif %}
7 |
8 |
9 |
10 | {% if field.errors %}
11 | {% for error in field.errors %}{{ error }} {% endfor %}
12 | {% endif %}
13 |
14 | {% if field.help_text %}
15 | {{ field.help_text|safe }}
16 | {% endif %}
17 |
18 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/langs/javascript.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% code javascript %}var coreapi = window.coreapi // Loaded by `coreapi.js`
3 | var schema = window.schema // Loaded by `schema.js`
4 |
5 | // Initialize a client
6 | var client = new coreapi.Client()
7 |
8 | // Interact with the API endpoint
9 | var action = [{% if section_key %}"{{ section_key }}", {% endif %}"{{ link_key }}"]
10 | {% if link.fields %}var params = {
11 | {% for field in link.fields %} {{ field.name }}: ...{% if not loop.last %},{% endif %}
12 | {% endfor %}}
13 | {% endif %}client.action(schema, action{% if link.fields %}, params{% endif %}).then(function(result) {
14 | // Return value is in 'result'
15 | }){% endcode %}
16 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/input.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 | {{ field.label }}
4 | {% endif %}
5 |
6 |
7 |
8 | {% if field.errors %}
9 | {% for error in field.errors %}
10 | {{ error }}
11 | {% endfor %}
12 | {% endif %}
13 |
14 | {% if field.help_text %}
15 | {{ field.help_text|safe }}
16 | {% endif %}
17 |
18 |
--------------------------------------------------------------------------------
/authtoken/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Token',
17 | fields=[
18 | ('key', models.CharField(primary_key=True, serialize=False, max_length=40)),
19 | ('created', models.DateTimeField(auto_now_add=True)),
20 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token', on_delete=models.CASCADE)),
21 | ],
22 | options={
23 | },
24 | bases=(models.Model,),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/textarea.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/radio.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load rest_framework %}
3 | {% trans "None" as none_choice %}
4 |
5 |
30 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | r"""
2 | ______ _____ _____ _____ __
3 | | ___ \ ___/ ___|_ _| / _| | |
4 | | |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__
5 | | /| __| `--. \ | | | _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ /
6 | | |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | <
7 | \_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_|
8 | """
9 |
10 | __title__ = 'Django REST framework'
11 | __version__ = '3.8.2'
12 | __author__ = 'Tom Christie'
13 | __license__ = 'BSD 2-Clause'
14 | __copyright__ = 'Copyright 2011-2018 Tom Christie'
15 |
16 | # Version synonym
17 | VERSION = __version__
18 |
19 | # Header encoding (see RFC5987)
20 | HTTP_HEADER_ENCODING = 'iso-8859-1'
21 |
22 | # Default datetime input and output formats
23 | ISO_8601 = 'iso-8601'
24 |
25 | default_app_config = 'rest_framework.apps.RestFrameworkConfig'
26 |
--------------------------------------------------------------------------------
/static/rest_framework/css/prettify.css:
--------------------------------------------------------------------------------
1 | .com { color: #93a1a1; }
2 | .lit { color: #195f91; }
3 | .pun, .opn, .clo { color: #93a1a1; }
4 | .fun { color: #dc322f; }
5 | .str, .atv { color: #D14; }
6 | .kwd, .prettyprint .tag { color: #1e347b; }
7 | .typ, .atn, .dec, .var { color: teal; }
8 | .pln { color: #48484c; }
9 |
10 | .prettyprint {
11 | padding: 8px;
12 | background-color: #f7f7f9;
13 | border: 1px solid #e1e1e8;
14 | }
15 | .prettyprint.linenums {
16 | -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
17 | -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
18 | box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
19 | }
20 |
21 | /* Specify class=linenums on a pre to get line numbering */
22 | ol.linenums {
23 | margin: 0 0 0 33px; /* IE indents via margin-left */
24 | }
25 | ol.linenums li {
26 | padding-left: 12px;
27 | color: #bebec5;
28 | line-height: 20px;
29 | text-shadow: 0 1px 0 #fff;
30 | }
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/input.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/select.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% if field.label %}
5 |
6 | {{ field.label }}
7 |
8 | {% endif %}
9 |
10 |
11 | {% if field.allow_null or field.allow_blank %}
12 | --------
13 | {% endif %}
14 | {% for select in field.iter_options %}
15 | {% if select.start_option_group %}
16 |
17 | {% elif select.end_option_group %}
18 |
19 | {% else %}
20 | {{ select.display_text }}
21 | {% endif %}
22 | {% endfor %}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/checks.py:
--------------------------------------------------------------------------------
1 | from django.core.checks import Tags, Warning, register
2 |
3 |
4 | @register(Tags.compatibility)
5 | def pagination_system_check(app_configs, **kwargs):
6 | errors = []
7 | # Use of default page size setting requires a default Paginator class
8 | from rest_framework.settings import api_settings
9 | if api_settings.PAGE_SIZE and not api_settings.DEFAULT_PAGINATION_CLASS:
10 | errors.append(
11 | Warning(
12 | "You have specified a default PAGE_SIZE pagination rest_framework setting,"
13 | "without specifying also a DEFAULT_PAGINATION_CLASS.",
14 | hint="The default for DEFAULT_PAGINATION_CLASS is None. "
15 | "In previous versions this was PageNumberPagination. "
16 | "If you wish to define PAGE_SIZE globally whilst defining "
17 | "pagination_class on a per-view basis you may silence this check.",
18 | id="rest_framework.W001"
19 | )
20 | )
21 | return errors
22 |
--------------------------------------------------------------------------------
/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | Login and logout views for the browsable API.
3 |
4 | Add these to your root URLconf if you're using the browsable API and
5 | your API requires authentication:
6 |
7 | urlpatterns = [
8 | ...
9 | url(r'^auth/', include('rest_framework.urls'))
10 | ]
11 |
12 | You should make sure your authentication settings include `SessionAuthentication`.
13 | """
14 | from __future__ import unicode_literals
15 |
16 | import django
17 | from django.conf.urls import url
18 | from django.contrib.auth import views
19 |
20 | if django.VERSION < (1, 11):
21 | login = views.login
22 | login_kwargs = {'template_name': 'rest_framework/login.html'}
23 | logout = views.logout
24 | else:
25 | login = views.LoginView.as_view(template_name='rest_framework/login.html')
26 | login_kwargs = {}
27 | logout = views.LogoutView.as_view()
28 |
29 |
30 | app_name = 'rest_framework'
31 | urlpatterns = [
32 | url(r'^login/$', login, login_kwargs, name='login'),
33 | url(r'^logout/$', logout, name='logout'),
34 | ]
35 |
--------------------------------------------------------------------------------
/templates/rest_framework/inline/select_multiple.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load rest_framework %}
3 | {% trans "No items to select." as no_items %}
4 |
5 |
6 | {% if field.label %}
7 |
8 | {{ field.label }}
9 |
10 | {% endif %}
11 |
12 |
13 | {% for select in field.iter_options %}
14 | {% if select.start_option_group %}
15 |
16 | {% elif select.end_option_group %}
17 |
18 | {% else %}
19 | {{ select.display_text }}
20 | {% endif %}
21 | {% empty %}
22 | {{ no_items }}
23 | {% endfor %}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/document.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 |
5 |
{{ document.title }}
6 | {% if document.description %}
7 |
{% render_markdown document.description %}
8 | {% endif %}
9 |
10 |
11 | {% for html in lang_intro_htmls %}
12 | {% include html %}
13 | {% endfor %}
14 |
15 |
16 | {% if document|data %}
17 | {% for section_key, section in document|data|items %}
18 | {% if section_key %}
19 | {{ section_key }}
20 |
21 | {% endif %}
22 |
23 | {% for link_key, link in section|schema_links|items %}
24 | {% include "rest_framework/docs/link.html" %}
25 | {% endfor %}
26 | {% endfor %}
27 |
28 | {% for link_key, link in document.links|items %}
29 | {% include "rest_framework/docs/link.html" %}
30 | {% endfor %}
31 | {% endif %}
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 项目介绍
2 | 本项目是对[django rest_framework框架](https://github.com/encode/django-rest-framework)的源码分析,方便对rest_framework进行源码解读,加深对rest_framework框架的理解。我将用在关键部分代码添加注释的方式对源码进行分析说明。
3 |
4 | 我将在个人博客上配合详细文字说明对源码分析的思路进行介绍,如果你也在学习rest_framework,请将本项目下载到你的Python安装文件下的\Lib\site-packages\目录下,然后根据博客上的说明,配合pycharm设置断点进行进行调试解读。
5 |
6 | ## 博客地址:
7 |
8 | ###### [一、django rest_framework源码之总体流程剖析](https://www.cnblogs.com/chenhuabin/p/9978468.html)
9 |
10 | ###### [二、django rest_framework源码之认证流程剖析](https://www.cnblogs.com/chenhuabin/p/9982833.html)
11 |
12 | ###### [三、django rest_framework源码之权限流程剖析](https://www.cnblogs.com/chenhuabin/p/9983722.html)
13 |
14 | ###### [四、django rest_framework源码之频率控制剖析](https://www.cnblogs.com/chenhuabin/p/9985898.html)
15 |
16 | ###### [五、django rest_framework源码之版本控制剖析](https://www.cnblogs.com/chenhuabin/p/9987467.html)
17 |
18 | ###### [六、django rest_framework源码之解析器剖析](https://www.cnblogs.com/chenhuabin/p/9988724.html)
19 |
20 | ###### [七、django rest_framework源码之视图](https://www.cnblogs.com/chenhuabin/p/9991293.html)
21 |
22 | 其他尚未完成部分将在后续进行更新……
23 |
24 |
25 |
--------------------------------------------------------------------------------
/utils/urls.py:
--------------------------------------------------------------------------------
1 | from django.utils.encoding import force_str
2 | from django.utils.six.moves.urllib import parse as urlparse
3 |
4 |
5 | def replace_query_param(url, key, val):
6 | """
7 | Given a URL and a key/val pair, set or replace an item in the query
8 | parameters of the URL, and return the new URL.
9 | """
10 | (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url))
11 | query_dict = urlparse.parse_qs(query, keep_blank_values=True)
12 | query_dict[force_str(key)] = [force_str(val)]
13 | query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
14 | return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
15 |
16 |
17 | def remove_query_param(url, key):
18 | """
19 | Given a URL and a key/val pair, remove an item in the query
20 | parameters of the URL, and return the new URL.
21 | """
22 | (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url))
23 | query_dict = urlparse.parse_qs(query, keep_blank_values=True)
24 | query_dict.pop(key, None)
25 | query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
26 | return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
27 |
--------------------------------------------------------------------------------
/authtoken/migrations/0002_auto_20160226_1747.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('authtoken', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterModelOptions(
16 | name='token',
17 | options={'verbose_name_plural': 'Tokens', 'verbose_name': 'Token'},
18 | ),
19 | migrations.AlterField(
20 | model_name='token',
21 | name='created',
22 | field=models.DateTimeField(verbose_name='Created', auto_now_add=True),
23 | ),
24 | migrations.AlterField(
25 | model_name='token',
26 | name='key',
27 | field=models.CharField(verbose_name='Key', max_length=40, primary_key=True, serialize=False),
28 | ),
29 | migrations.AlterField(
30 | model_name='token',
31 | name='user',
32 | field=models.OneToOneField(to=settings.AUTH_USER_MODEL, verbose_name='User', related_name='auth_token', on_delete=models.CASCADE),
33 | ),
34 | ]
35 |
--------------------------------------------------------------------------------
/utils/json.py:
--------------------------------------------------------------------------------
1 | """
2 | Wrapper for the builtin json module that ensures compliance with the JSON spec.
3 |
4 | REST framework should always import this wrapper module in order to maintain
5 | spec-compliant encoding/decoding. Support for non-standard features should be
6 | handled by users at the renderer and parser layer.
7 | """
8 |
9 | from __future__ import absolute_import
10 |
11 | import functools
12 | import json # noqa
13 |
14 |
15 | def strict_constant(o):
16 | raise ValueError('Out of range float values are not JSON compliant: ' + repr(o))
17 |
18 |
19 | @functools.wraps(json.dump)
20 | def dump(*args, **kwargs):
21 | kwargs.setdefault('allow_nan', False)
22 | return json.dump(*args, **kwargs)
23 |
24 |
25 | @functools.wraps(json.dumps)
26 | def dumps(*args, **kwargs):
27 | kwargs.setdefault('allow_nan', False)
28 | return json.dumps(*args, **kwargs)
29 |
30 |
31 | @functools.wraps(json.load)
32 | def load(*args, **kwargs):
33 | kwargs.setdefault('parse_constant', strict_constant)
34 | return json.load(*args, **kwargs)
35 |
36 |
37 | @functools.wraps(json.loads)
38 | def loads(*args, **kwargs):
39 | kwargs.setdefault('parse_constant', strict_constant)
40 | return json.loads(*args, **kwargs)
41 |
--------------------------------------------------------------------------------
/schemas/views.py:
--------------------------------------------------------------------------------
1 | """
2 | views.py # Houses `SchemaView`, `APIView` subclass.
3 |
4 | See schemas.__init__.py for package overview.
5 | """
6 | from rest_framework import exceptions, renderers
7 | from rest_framework.response import Response
8 | from rest_framework.settings import api_settings
9 | from rest_framework.views import APIView
10 |
11 |
12 | class SchemaView(APIView):
13 | _ignore_model_permissions = True
14 | schema = None # exclude from schema
15 | renderer_classes = None
16 | schema_generator = None
17 | public = False
18 |
19 | def __init__(self, *args, **kwargs):
20 | super(SchemaView, self).__init__(*args, **kwargs)
21 | if self.renderer_classes is None:
22 | if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
23 | self.renderer_classes = [
24 | renderers.CoreJSONRenderer,
25 | renderers.BrowsableAPIRenderer,
26 | ]
27 | else:
28 | self.renderer_classes = [renderers.CoreJSONRenderer]
29 |
30 | def get(self, request, *args, **kwargs):
31 | schema = self.schema_generator.get_schema(request, self.public)
32 | if schema is None:
33 | raise exceptions.PermissionDenied()
34 | return Response(schema)
35 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/auth/session.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 |
36 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/select.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 | {% if field.label %}
5 |
6 | {{ field.label }}
7 |
8 | {% endif %}
9 |
10 |
11 | {% if field.allow_null or field.allow_blank %}
12 | --------
13 | {% endif %}
14 | {% for select in field.iter_options %}
15 | {% if select.start_option_group %}
16 |
17 | {% elif select.end_option_group %}
18 |
19 | {% else %}
20 | {{ select.display_text }}
21 | {% endif %}
22 | {% endfor %}
23 |
24 |
25 | {% if field.errors %}
26 | {% for error in field.errors %}
27 | {{ error }}
28 | {% endfor %}
29 | {% endif %}
30 |
31 | {% if field.help_text %}
32 | {{ field.help_text|safe }}
33 | {% endif %}
34 |
35 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/checkbox_multiple.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
38 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/select_multiple.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load rest_framework %}
3 | {% trans "No items to select." as no_items %}
4 |
5 |
6 | {% if field.label %}
7 |
8 | {{ field.label }}
9 |
10 | {% endif %}
11 |
12 |
13 | {% for select in field.iter_options %}
14 | {% if select.start_option_group %}
15 |
16 | {% elif select.end_option_group %}
17 |
18 | {% else %}
19 | {{ select.display_text }}
20 | {% endif %}
21 | {% empty %}
22 | {{ no_items }}
23 | {% endfor %}
24 |
25 |
26 | {% if field.errors %}
27 | {% for error in field.errors %}{{ error }} {% endfor %}
28 | {% endif %}
29 |
30 | {% if field.help_text %}
31 | {{ field.help_text|safe }}
32 | {% endif %}
33 |
34 |
--------------------------------------------------------------------------------
/static/rest_framework/docs/css/jquery.json-view.min.css:
--------------------------------------------------------------------------------
1 | .json-view{position:relative}
2 | .json-view .collapser{width:20px;height:18px;display:block;position:absolute;left:-1.7em;top:-.2em;z-index:5;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpiYGBgOADE%2F3Hgw0DM4IRHgSsDFOzFInmMAQnY49ONzZRjDFiADT7dMLALiE8y4AGW6LoBAgwAuIkf%2F%2FB7O9sAAAAASUVORK5CYII%3D);background-repeat:no-repeat;background-position:center center;opacity:.5;cursor:pointer}
3 | .json-view .collapsed{-ms-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-khtml-transform:rotate(-90deg);-webkit-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}
4 | .json-view .bl{display:block;padding-left:20px;margin-left:-20px;position:relative}
5 | .json-view{font-family:monospace}
6 | .json-view ul{list-style-type:none;padding-left:2em;border-left:1px dotted;margin:.3em}
7 | .json-view ul li{position:relative}
8 | .json-view .comments,.json-view .dots{display:none;-moz-user-select:none;-ms-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}
9 | .json-view .comments{padding-left:.8em;font-style:italic;color:#888}
10 | .json-view .bool,.json-view .null,.json-view .num,.json-view .undef{font-weight:700;color:#1A01CC}
11 | .json-view .str{color:#800}
--------------------------------------------------------------------------------
/authtoken/serializers.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import ugettext_lazy as _
2 |
3 | from rest_framework import serializers
4 | from rest_framework.compat import authenticate
5 |
6 |
7 | class AuthTokenSerializer(serializers.Serializer):
8 | username = serializers.CharField(label=_("Username"))
9 | password = serializers.CharField(
10 | label=_("Password"),
11 | style={'input_type': 'password'},
12 | trim_whitespace=False
13 | )
14 |
15 | def validate(self, attrs):
16 | username = attrs.get('username')
17 | password = attrs.get('password')
18 |
19 | if username and password:
20 | user = authenticate(request=self.context.get('request'),
21 | username=username, password=password)
22 |
23 | # The authenticate call simply returns None for is_active=False
24 | # users. (Assuming the default ModelBackend authentication
25 | # backend.)
26 | if not user:
27 | msg = _('Unable to log in with provided credentials.')
28 | raise serializers.ValidationError(msg, code='authorization')
29 | else:
30 | msg = _('Must include "username" and "password".')
31 | raise serializers.ValidationError(msg, code='authorization')
32 |
33 | attrs['user'] = user
34 | return attrs
35 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/checkbox_multiple.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
40 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/select.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
37 |
--------------------------------------------------------------------------------
/templates/rest_framework/pagination/numbers.html:
--------------------------------------------------------------------------------
1 |
48 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/select_multiple.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load rest_framework %}
3 |
4 | {% trans "No items to select." as no_items %}
5 |
6 |
39 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/auth/basic.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 |
39 |
--------------------------------------------------------------------------------
/authtoken/models.py:
--------------------------------------------------------------------------------
1 | import binascii
2 | import os
3 |
4 | from django.conf import settings
5 | from django.db import models
6 | from django.utils.encoding import python_2_unicode_compatible
7 | from django.utils.translation import ugettext_lazy as _
8 |
9 |
10 | @python_2_unicode_compatible
11 | class Token(models.Model):
12 | """
13 | The default authorization token model.
14 | """
15 | key = models.CharField(_("Key"), max_length=40, primary_key=True)
16 | user = models.OneToOneField(
17 | settings.AUTH_USER_MODEL, related_name='auth_token',
18 | on_delete=models.CASCADE, verbose_name=_("User")
19 | )
20 | created = models.DateTimeField(_("Created"), auto_now_add=True)
21 |
22 | class Meta:
23 | # Work around for a bug in Django:
24 | # https://code.djangoproject.com/ticket/19422
25 | #
26 | # Also see corresponding ticket:
27 | # https://github.com/encode/django-rest-framework/issues/705
28 | abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
29 | verbose_name = _("Token")
30 | verbose_name_plural = _("Tokens")
31 |
32 | def save(self, *args, **kwargs):
33 | if not self.key:
34 | self.key = self.generate_key()
35 | return super(Token, self).save(*args, **kwargs)
36 |
37 | def generate_key(self):
38 | return binascii.hexlify(os.urandom(20)).decode()
39 |
40 | def __str__(self):
41 | return self.key
42 |
--------------------------------------------------------------------------------
/static/rest_framework/js/default.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | // JSON highlighting.
3 | prettyPrint();
4 |
5 | // Bootstrap tooltips.
6 | $('.js-tooltip').tooltip({
7 | delay: 1000,
8 | container: 'body'
9 | });
10 |
11 | // Deal with rounded tab styling after tab clicks.
12 | $('a[data-toggle="tab"]:first').on('shown', function(e) {
13 | $(e.target).parents('.tabbable').addClass('first-tab-active');
14 | });
15 |
16 | $('a[data-toggle="tab"]:not(:first)').on('shown', function(e) {
17 | $(e.target).parents('.tabbable').removeClass('first-tab-active');
18 | });
19 |
20 | $('a[data-toggle="tab"]').click(function() {
21 | document.cookie = "tabstyle=" + this.name + "; path=/";
22 | });
23 |
24 | // Store tab preference in cookies & display appropriate tab on load.
25 | var selectedTab = null;
26 | var selectedTabName = getCookie('tabstyle');
27 |
28 | if (selectedTabName) {
29 | selectedTabName = selectedTabName.replace(/[^a-z-]/g, '');
30 | }
31 |
32 | if (selectedTabName) {
33 | selectedTab = $('.form-switcher a[name=' + selectedTabName + ']');
34 | }
35 |
36 | if (selectedTab && selectedTab.length > 0) {
37 | // Display whichever tab is selected.
38 | selectedTab.tab('show');
39 | } else {
40 | // If no tab selected, display rightmost tab.
41 | $('.form-switcher a:first').tab('show');
42 | }
43 |
44 | $(window).load(function() {
45 | $('#errorModal').modal('show');
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/utils/humanize_datetime.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper functions that convert strftime formats into more readable representations.
3 | """
4 | from rest_framework import ISO_8601
5 |
6 |
7 | def datetime_formats(formats):
8 | format = ', '.join(formats).replace(
9 | ISO_8601,
10 | 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'
11 | )
12 | return humanize_strptime(format)
13 |
14 |
15 | def date_formats(formats):
16 | format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]')
17 | return humanize_strptime(format)
18 |
19 |
20 | def time_formats(formats):
21 | format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]')
22 | return humanize_strptime(format)
23 |
24 |
25 | def humanize_strptime(format_string):
26 | # Note that we're missing some of the locale specific mappings that
27 | # don't really make sense.
28 | mapping = {
29 | "%Y": "YYYY",
30 | "%y": "YY",
31 | "%m": "MM",
32 | "%b": "[Jan-Dec]",
33 | "%B": "[January-December]",
34 | "%d": "DD",
35 | "%H": "hh",
36 | "%I": "hh", # Requires '%p' to differentiate from '%H'.
37 | "%M": "mm",
38 | "%S": "ss",
39 | "%f": "uuuuuu",
40 | "%a": "[Mon-Sun]",
41 | "%A": "[Monday-Sunday]",
42 | "%p": "[AM|PM]",
43 | "%z": "[+HHMM|-HHMM]"
44 | }
45 | for key, val in mapping.items():
46 | format_string = format_string.replace(key, val)
47 | return format_string
48 |
--------------------------------------------------------------------------------
/static/rest_framework/css/default.css:
--------------------------------------------------------------------------------
1 | /* The navbar is fixed at >= 980px wide, so add padding to the body to prevent
2 | content running up underneath it. */
3 |
4 | h1 {
5 | font-weight: 300;
6 | }
7 |
8 | h2, h3 {
9 | font-weight: 300;
10 | }
11 |
12 | .resource-description, .response-info {
13 | margin-bottom: 2em;
14 | }
15 |
16 | .version:before {
17 | content: "v";
18 | opacity: 0.6;
19 | padding-right: 0.25em;
20 | }
21 |
22 | .version {
23 | font-size: 70%;
24 | }
25 |
26 | .format-option {
27 | font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
28 | }
29 |
30 | .button-form {
31 | float: right;
32 | margin-right: 1em;
33 | }
34 |
35 | td.nested {
36 | padding: 0 !important;
37 | }
38 |
39 | td.nested > table {
40 | margin: 0;
41 | }
42 |
43 | form select, form input, form textarea {
44 | width: 90%;
45 | }
46 |
47 | form select[multiple] {
48 | height: 150px;
49 | }
50 |
51 | /* To allow tooltips to work on disabled elements */
52 | .disabled-tooltip-shield {
53 | position: absolute;
54 | top: 0;
55 | right: 0;
56 | bottom: 0;
57 | left: 0;
58 | }
59 |
60 | .errorlist {
61 | margin-top: 0.5em;
62 | }
63 |
64 | pre {
65 | overflow: auto;
66 | word-wrap: normal;
67 | white-space: pre;
68 | font-size: 12px;
69 | }
70 |
71 | .page-header {
72 | border-bottom: none;
73 | padding-bottom: 0px;
74 | }
75 |
76 | #filtersModal form input[type=submit] {
77 | width: auto;
78 | }
79 |
80 | #filtersModal .modal-body h2 {
81 | margin-top: 0
82 | }
83 |
--------------------------------------------------------------------------------
/authtoken/management/commands/drf_create_token.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.core.management.base import BaseCommand, CommandError
3 |
4 | from rest_framework.authtoken.models import Token
5 |
6 | UserModel = get_user_model()
7 |
8 |
9 | class Command(BaseCommand):
10 | help = 'Create DRF Token for a given user'
11 |
12 | def create_user_token(self, username, reset_token):
13 | user = UserModel._default_manager.get_by_natural_key(username)
14 |
15 | if reset_token:
16 | Token.objects.filter(user=user).delete()
17 |
18 | token = Token.objects.get_or_create(user=user)
19 | return token[0]
20 |
21 | def add_arguments(self, parser):
22 | parser.add_argument('username', type=str)
23 |
24 | parser.add_argument(
25 | '-r',
26 | '--reset',
27 | action='store_true',
28 | dest='reset_token',
29 | default=False,
30 | help='Reset existing User token and create a new one',
31 | )
32 |
33 | def handle(self, *args, **options):
34 | username = options['username']
35 | reset_token = options['reset_token']
36 |
37 | try:
38 | token = self.create_user_token(username, reset_token)
39 | except UserModel.DoesNotExist:
40 | raise CommandError(
41 | 'Cannot create the Token: user {0} does not exist'.format(
42 | username)
43 | )
44 | self.stdout.write(
45 | 'Generated token {0} for user {1}'.format(token.key, username))
46 |
--------------------------------------------------------------------------------
/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | rest_framework.schemas
3 |
4 | schemas:
5 | __init__.py
6 | generators.py # Top-down schema generation
7 | inspectors.py # Per-endpoint view introspection
8 | utils.py # Shared helper functions
9 | views.py # Houses `SchemaView`, `APIView` subclass.
10 |
11 | We expose a minimal "public" API directly from `schemas`. This covers the
12 | basic use-cases:
13 |
14 | from rest_framework.schemas import (
15 | AutoSchema,
16 | ManualSchema,
17 | get_schema_view,
18 | SchemaGenerator,
19 | )
20 |
21 | Other access should target the submodules directly
22 | """
23 | from rest_framework.settings import api_settings
24 |
25 | from .generators import SchemaGenerator
26 | from .inspectors import AutoSchema, DefaultSchema, ManualSchema # noqa
27 |
28 |
29 | def get_schema_view(
30 | title=None, url=None, description=None, urlconf=None, renderer_classes=None,
31 | public=False, patterns=None, generator_class=SchemaGenerator,
32 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
33 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
34 | """
35 | Return a schema view.
36 | """
37 | # Avoid import cycle on APIView
38 | from .views import SchemaView
39 | generator = generator_class(
40 | title=title, url=url, description=description,
41 | urlconf=urlconf, patterns=patterns,
42 | )
43 | return SchemaView.as_view(
44 | renderer_classes=renderer_classes,
45 | schema_generator=generator,
46 | public=public,
47 | authentication_classes=authentication_classes,
48 | permission_classes=permission_classes,
49 | )
50 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/auth/token.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 |
37 |
--------------------------------------------------------------------------------
/static/rest_framework/js/csrf.js:
--------------------------------------------------------------------------------
1 | function getCookie(name) {
2 | var cookieValue = null;
3 |
4 | if (document.cookie && document.cookie != '') {
5 | var cookies = document.cookie.split(';');
6 |
7 | for (var i = 0; i < cookies.length; i++) {
8 | var cookie = jQuery.trim(cookies[i]);
9 |
10 | // Does this cookie string begin with the name we want?
11 | if (cookie.substring(0, name.length + 1) == (name + '=')) {
12 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
13 | break;
14 | }
15 | }
16 | }
17 |
18 | return cookieValue;
19 | }
20 |
21 | function csrfSafeMethod(method) {
22 | // these HTTP methods do not require CSRF protection
23 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
24 | }
25 |
26 | function sameOrigin(url) {
27 | // test that a given url is a same-origin URL
28 | // url could be relative or scheme relative or absolute
29 | var host = document.location.host; // host + port
30 | var protocol = document.location.protocol;
31 | var sr_origin = '//' + host;
32 | var origin = protocol + sr_origin;
33 |
34 | // Allow absolute or scheme relative URLs to same origin
35 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
36 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
37 | // or any other URL that isn't scheme relative or absolute i.e relative.
38 | !(/^(\/\/|http:|https:).*/.test(url));
39 | }
40 |
41 | var csrftoken = getCookie(window.drf.csrfCookieName);
42 |
43 | $.ajaxSetup({
44 | beforeSend: function(xhr, settings) {
45 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
46 | // Send the token to same-origin, relative URLs only.
47 | // Send the token only if the method warrants CSRF protection
48 | // Using the CSRFToken value acquired earlier
49 | xhr.setRequestHeader(window.drf.csrfHeaderName, csrftoken);
50 | }
51 | }
52 | });
53 |
--------------------------------------------------------------------------------
/templates/rest_framework/horizontal/radio.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load rest_framework %}
3 |
4 | {% trans "None" as none_choice %}
5 |
6 |
58 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/interact.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 |
52 |
--------------------------------------------------------------------------------
/templates/rest_framework/vertical/radio.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load rest_framework %}
3 | {% trans "None" as none_choice %}
4 |
5 |
58 |
--------------------------------------------------------------------------------
/authtoken/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import parsers, renderers
2 | from rest_framework.authtoken.models import Token
3 | from rest_framework.authtoken.serializers import AuthTokenSerializer
4 | from rest_framework.compat import coreapi, coreschema
5 | from rest_framework.response import Response
6 | from rest_framework.schemas import ManualSchema
7 | from rest_framework.views import APIView
8 |
9 |
10 | class ObtainAuthToken(APIView):
11 | throttle_classes = ()
12 | permission_classes = ()
13 | parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
14 | renderer_classes = (renderers.JSONRenderer,)
15 | serializer_class = AuthTokenSerializer
16 | if coreapi is not None and coreschema is not None:
17 | schema = ManualSchema(
18 | fields=[
19 | coreapi.Field(
20 | name="username",
21 | required=True,
22 | location='form',
23 | schema=coreschema.String(
24 | title="Username",
25 | description="Valid username for authentication",
26 | ),
27 | ),
28 | coreapi.Field(
29 | name="password",
30 | required=True,
31 | location='form',
32 | schema=coreschema.String(
33 | title="Password",
34 | description="Valid password for authentication",
35 | ),
36 | ),
37 | ],
38 | encoding="application/json",
39 | )
40 |
41 | def post(self, request, *args, **kwargs):
42 | serializer = self.serializer_class(data=request.data,
43 | context={'request': request})
44 | serializer.is_valid(raise_exception=True)
45 | user = serializer.validated_data['user']
46 | token, created = Token.objects.get_or_create(user=user)
47 | return Response({'token': token.key})
48 |
49 |
50 | obtain_auth_token = ObtainAuthToken.as_view()
51 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/error.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Error Rendering Schema
10 |
11 |
12 |
13 |
14 |
15 | Error
16 |
17 |
18 | {{ data }}
19 |
20 |
21 |
22 | {% if debug is True %}
23 |
24 | Additional Information
25 | Note: You are seeing this message because DEBUG==True.
26 |
27 | Seeing this page is usually a configuration error: are your
28 | DEFAULT_AUTHENTICATION_CLASSES or DEFAULT_PERMISSION_CLASSES
29 | being applied unexpectedly?
30 |
31 | Your response status code is: {{ response.status_code }}
32 |
33 | 401 Unauthorised.
34 |
35 | Do you have SessionAuthentication enabled?
36 | Are you logged in?
37 |
38 |
39 |
40 | 403 Forbidden.
41 |
42 | Do you have sufficient permissions to access this view?
43 | Is you schema non-empty? (An empty schema will lead to a permission denied error being raised.)
44 |
45 |
46 |
47 | Most commonly the intended solution is to disable authentication and permissions
48 | when including the docs urls:
49 |
50 |
51 | url(r'^docs/', include_docs_urls(title='Your API',
52 | authentication_classes=[],
53 | permission_classes=[])),
54 |
55 |
56 |
57 | Overriding this template
58 |
59 | If you wish access to your docs to be authenticated you may override this template
60 | at rest_framework/docs/error.html.
61 |
62 | The available context is: data the error dict above, request,
63 | response and the debug flag.
64 |
65 | {% endif %}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/utils/breadcrumbs.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.urls import get_script_prefix, resolve
4 |
5 |
6 | def get_breadcrumbs(url, request=None):
7 | """
8 | Given a url returns a list of breadcrumbs, which are each a
9 | tuple of (name, url).
10 | """
11 | from rest_framework.reverse import preserve_builtin_query_params
12 | from rest_framework.views import APIView
13 |
14 | def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen):
15 | """
16 | Add tuples of (name, url) to the breadcrumbs list,
17 | progressively chomping off parts of the url.
18 | """
19 | try:
20 | (view, unused_args, unused_kwargs) = resolve(url)
21 | except Exception:
22 | pass
23 | else:
24 | # Check if this is a REST framework view,
25 | # and if so add it to the breadcrumbs
26 | cls = getattr(view, 'cls', None)
27 | initkwargs = getattr(view, 'initkwargs', {})
28 | if cls is not None and issubclass(cls, APIView):
29 | # Don't list the same view twice in a row.
30 | # Probably an optional trailing slash.
31 | if not seen or seen[-1] != view:
32 | c = cls(**initkwargs)
33 | c.suffix = getattr(view, 'suffix', None)
34 | name = c.get_view_name()
35 | insert_url = preserve_builtin_query_params(prefix + url, request)
36 | breadcrumbs_list.insert(0, (name, insert_url))
37 | seen.append(view)
38 |
39 | if url == '':
40 | # All done
41 | return breadcrumbs_list
42 |
43 | elif url.endswith('/'):
44 | # Drop trailing slash off the end and continue to try to
45 | # resolve more breadcrumbs
46 | url = url.rstrip('/')
47 | return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen)
48 |
49 | # Drop trailing non-slash off the end and continue to try to
50 | # resolve more breadcrumbs
51 | url = url[:url.rfind('/') + 1]
52 | return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen)
53 |
54 | prefix = get_script_prefix().rstrip('/')
55 | url = url[len(prefix):]
56 | return breadcrumbs_recursive(url, [], prefix, [])
57 |
--------------------------------------------------------------------------------
/static/rest_framework/docs/css/highlight.css:
--------------------------------------------------------------------------------
1 | /*
2 | This is the GitHub theme for highlight.js
3 |
4 | github.com style (c) Vasily Polovnyov
5 |
6 | */
7 |
8 | .hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 0.5em;
12 | color: #333;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs-comment,
17 | .diff .hljs-header,
18 | .hljs-javadoc {
19 | color: #998;
20 | font-style: italic;
21 | }
22 |
23 | .hljs-keyword,
24 | .css .rule .hljs-keyword,
25 | .hljs-winutils,
26 | .nginx .hljs-title,
27 | .hljs-subst,
28 | .hljs-request,
29 | .hljs-status {
30 | color: #333;
31 | font-weight: bold;
32 | }
33 |
34 | .hljs-number,
35 | .hljs-hexcolor,
36 | .ruby .hljs-constant {
37 | color: #008080;
38 | }
39 |
40 | .hljs-string,
41 | .hljs-tag .hljs-value,
42 | .hljs-phpdoc,
43 | .hljs-dartdoc,
44 | .tex .hljs-formula {
45 | color: #d14;
46 | }
47 |
48 | .hljs-title,
49 | .hljs-id,
50 | .scss .hljs-preprocessor {
51 | color: #900;
52 | font-weight: bold;
53 | }
54 |
55 | .hljs-list .hljs-keyword,
56 | .hljs-subst {
57 | font-weight: normal;
58 | }
59 |
60 | .hljs-class .hljs-title,
61 | .hljs-type,
62 | .vhdl .hljs-literal,
63 | .tex .hljs-command {
64 | color: #458;
65 | font-weight: bold;
66 | }
67 |
68 | .hljs-tag,
69 | .hljs-tag .hljs-title,
70 | .hljs-rule .hljs-property,
71 | .django .hljs-tag .hljs-keyword {
72 | color: #000080;
73 | font-weight: normal;
74 | }
75 |
76 | .hljs-attribute,
77 | .hljs-variable,
78 | .lisp .hljs-body,
79 | .hljs-name {
80 | color: #008080;
81 | }
82 |
83 | .hljs-regexp {
84 | color: #009926;
85 | }
86 |
87 | .hljs-symbol,
88 | .ruby .hljs-symbol .hljs-string,
89 | .lisp .hljs-keyword,
90 | .clojure .hljs-keyword,
91 | .scheme .hljs-keyword,
92 | .tex .hljs-special,
93 | .hljs-prompt {
94 | color: #990073;
95 | }
96 |
97 | .hljs-built_in {
98 | color: #0086b3;
99 | }
100 |
101 | .hljs-preprocessor,
102 | .hljs-pragma,
103 | .hljs-pi,
104 | .hljs-doctype,
105 | .hljs-shebang,
106 | .hljs-cdata {
107 | color: #999;
108 | font-weight: bold;
109 | }
110 |
111 | .hljs-deletion {
112 | background: #fdd;
113 | }
114 |
115 | .hljs-addition {
116 | background: #dfd;
117 | }
118 |
119 | .diff .hljs-change {
120 | background: #0086b3;
121 | }
122 |
123 | .hljs-chunk {
124 | color: #aaa;
125 | }
126 |
--------------------------------------------------------------------------------
/utils/html.py:
--------------------------------------------------------------------------------
1 | """
2 | Helpers for dealing with HTML input.
3 | """
4 | import re
5 |
6 | from django.utils.datastructures import MultiValueDict
7 |
8 |
9 | def is_html_input(dictionary):
10 | # MultiDict type datastructures are used to represent HTML form input,
11 | # which may have more than one value for each key.
12 | return hasattr(dictionary, 'getlist')
13 |
14 |
15 | def parse_html_list(dictionary, prefix=''):
16 | """
17 | Used to support list values in HTML forms.
18 | Supports lists of primitives and/or dictionaries.
19 |
20 | * List of primitives.
21 |
22 | {
23 | '[0]': 'abc',
24 | '[1]': 'def',
25 | '[2]': 'hij'
26 | }
27 | -->
28 | [
29 | 'abc',
30 | 'def',
31 | 'hij'
32 | ]
33 |
34 | * List of dictionaries.
35 |
36 | {
37 | '[0]foo': 'abc',
38 | '[0]bar': 'def',
39 | '[1]foo': 'hij',
40 | '[1]bar': 'klm',
41 | }
42 | -->
43 | [
44 | {'foo': 'abc', 'bar': 'def'},
45 | {'foo': 'hij', 'bar': 'klm'}
46 | ]
47 | """
48 | ret = {}
49 | regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix))
50 | for field, value in dictionary.items():
51 | match = regex.match(field)
52 | if not match:
53 | continue
54 | index, key = match.groups()
55 | index = int(index)
56 | if not key:
57 | ret[index] = value
58 | elif isinstance(ret.get(index), dict):
59 | ret[index][key] = value
60 | else:
61 | ret[index] = MultiValueDict({key: [value]})
62 | return [ret[item] for item in sorted(ret)]
63 |
64 |
65 | def parse_html_dict(dictionary, prefix=''):
66 | """
67 | Used to support dictionary values in HTML forms.
68 |
69 | {
70 | 'profile.username': 'example',
71 | 'profile.email': 'example@example.com',
72 | }
73 | -->
74 | {
75 | 'profile': {
76 | 'username': 'example',
77 | 'email': 'example@example.com'
78 | }
79 | }
80 | """
81 | ret = MultiValueDict()
82 | regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
83 | for field in dictionary:
84 | match = regex.match(field)
85 | if not match:
86 | continue
87 | key = match.groups()[0]
88 | value = dictionary.getlist(field)
89 | ret.setlist(key, value)
90 |
91 | return ret
92 |
--------------------------------------------------------------------------------
/reverse.py:
--------------------------------------------------------------------------------
1 | """
2 | Provide urlresolver functions that return fully qualified URLs or view names
3 | """
4 | from __future__ import unicode_literals
5 |
6 | from django.urls import NoReverseMatch
7 | from django.urls import reverse as django_reverse
8 | from django.utils import six
9 | from django.utils.functional import lazy
10 |
11 | from rest_framework.settings import api_settings
12 | from rest_framework.utils.urls import replace_query_param
13 |
14 |
15 | def preserve_builtin_query_params(url, request=None):
16 | """
17 | Given an incoming request, and an outgoing URL representation,
18 | append the value of any built-in query parameters.
19 | """
20 | if request is None:
21 | return url
22 |
23 | overrides = [
24 | api_settings.URL_FORMAT_OVERRIDE,
25 | ]
26 |
27 | for param in overrides:
28 | if param and (param in request.GET):
29 | value = request.GET[param]
30 | url = replace_query_param(url, param, value)
31 |
32 | return url
33 |
34 |
35 | def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
36 | """
37 | If versioning is being used then we pass any `reverse` calls through
38 | to the versioning scheme instance, so that the resulting URL
39 | can be modified if needed.
40 | """
41 | scheme = getattr(request, 'versioning_scheme', None)
42 | if scheme is not None:
43 | try:
44 | url = scheme.reverse(viewname, args, kwargs, request, format, **extra)
45 | except NoReverseMatch:
46 | # In case the versioning scheme reversal fails, fallback to the
47 | # default implementation
48 | url = _reverse(viewname, args, kwargs, request, format, **extra)
49 | else:
50 | url = _reverse(viewname, args, kwargs, request, format, **extra)
51 |
52 | return preserve_builtin_query_params(url, request)
53 |
54 |
55 | def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
56 | """
57 | Same as `django.urls.reverse`, but optionally takes a request
58 | and returns a fully qualified URL, using the request to get the base URL.
59 | """
60 | if format is not None:
61 | kwargs = kwargs or {}
62 | kwargs['format'] = format
63 | url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
64 | if request:
65 | return request.build_absolute_uri(url)
66 | return url
67 |
68 |
69 | reverse_lazy = lazy(reverse, six.text_type)
70 |
--------------------------------------------------------------------------------
/status.py:
--------------------------------------------------------------------------------
1 | """
2 | Descriptive HTTP status codes, for code readability.
3 |
4 | See RFC 2616 - https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
5 | And RFC 6585 - https://tools.ietf.org/html/rfc6585
6 | And RFC 4918 - https://tools.ietf.org/html/rfc4918
7 | """
8 | from __future__ import unicode_literals
9 |
10 |
11 | def is_informational(code):
12 | return 100 <= code <= 199
13 |
14 |
15 | def is_success(code):
16 | return 200 <= code <= 299
17 |
18 |
19 | def is_redirect(code):
20 | return 300 <= code <= 399
21 |
22 |
23 | def is_client_error(code):
24 | return 400 <= code <= 499
25 |
26 |
27 | def is_server_error(code):
28 | return 500 <= code <= 599
29 |
30 |
31 | HTTP_100_CONTINUE = 100
32 | HTTP_101_SWITCHING_PROTOCOLS = 101
33 | HTTP_200_OK = 200
34 | HTTP_201_CREATED = 201
35 | HTTP_202_ACCEPTED = 202
36 | HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
37 | HTTP_204_NO_CONTENT = 204
38 | HTTP_205_RESET_CONTENT = 205
39 | HTTP_206_PARTIAL_CONTENT = 206
40 | HTTP_207_MULTI_STATUS = 207
41 | HTTP_300_MULTIPLE_CHOICES = 300
42 | HTTP_301_MOVED_PERMANENTLY = 301
43 | HTTP_302_FOUND = 302
44 | HTTP_303_SEE_OTHER = 303
45 | HTTP_304_NOT_MODIFIED = 304
46 | HTTP_305_USE_PROXY = 305
47 | HTTP_306_RESERVED = 306
48 | HTTP_307_TEMPORARY_REDIRECT = 307
49 | HTTP_400_BAD_REQUEST = 400
50 | HTTP_401_UNAUTHORIZED = 401
51 | HTTP_402_PAYMENT_REQUIRED = 402
52 | HTTP_403_FORBIDDEN = 403
53 | HTTP_404_NOT_FOUND = 404
54 | HTTP_405_METHOD_NOT_ALLOWED = 405
55 | HTTP_406_NOT_ACCEPTABLE = 406
56 | HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
57 | HTTP_408_REQUEST_TIMEOUT = 408
58 | HTTP_409_CONFLICT = 409
59 | HTTP_410_GONE = 410
60 | HTTP_411_LENGTH_REQUIRED = 411
61 | HTTP_412_PRECONDITION_FAILED = 412
62 | HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
63 | HTTP_414_REQUEST_URI_TOO_LONG = 414
64 | HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
65 | HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
66 | HTTP_417_EXPECTATION_FAILED = 417
67 | HTTP_422_UNPROCESSABLE_ENTITY = 422
68 | HTTP_423_LOCKED = 423
69 | HTTP_424_FAILED_DEPENDENCY = 424
70 | HTTP_428_PRECONDITION_REQUIRED = 428
71 | HTTP_429_TOO_MANY_REQUESTS = 429
72 | HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
73 | HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
74 | HTTP_500_INTERNAL_SERVER_ERROR = 500
75 | HTTP_501_NOT_IMPLEMENTED = 501
76 | HTTP_502_BAD_GATEWAY = 502
77 | HTTP_503_SERVICE_UNAVAILABLE = 503
78 | HTTP_504_GATEWAY_TIMEOUT = 504
79 | HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
80 | HTTP_507_INSUFFICIENT_STORAGE = 507
81 | HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
82 |
--------------------------------------------------------------------------------
/utils/formatting.py:
--------------------------------------------------------------------------------
1 | """
2 | Utility functions to return a formatted name and description for a given view.
3 | """
4 | from __future__ import unicode_literals
5 |
6 | import re
7 |
8 | from django.utils.encoding import force_text
9 | from django.utils.html import escape
10 | from django.utils.safestring import mark_safe
11 |
12 | from rest_framework.compat import apply_markdown
13 |
14 |
15 | def remove_trailing_string(content, trailing):
16 | """
17 | Strip trailing component `trailing` from `content` if it exists.
18 | Used when generating names from view classes.
19 | """
20 | if content.endswith(trailing) and content != trailing:
21 | return content[:-len(trailing)]
22 | return content
23 |
24 |
25 | def dedent(content):
26 | """
27 | Remove leading indent from a block of text.
28 | Used when generating descriptions from docstrings.
29 |
30 | Note that python's `textwrap.dedent` doesn't quite cut it,
31 | as it fails to dedent multiline docstrings that include
32 | unindented text on the initial line.
33 | """
34 | content = force_text(content)
35 | lines = [line for line in content.splitlines()[1:] if line.lstrip()]
36 |
37 | # unindent the content if needed
38 | if lines:
39 | whitespace_counts = min([len(line) - len(line.lstrip(' ')) for line in lines])
40 | tab_counts = min([len(line) - len(line.lstrip('\t')) for line in lines])
41 | if whitespace_counts:
42 | whitespace_pattern = '^' + (' ' * whitespace_counts)
43 | content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
44 | elif tab_counts:
45 | whitespace_pattern = '^' + ('\t' * tab_counts)
46 | content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
47 | return content.strip()
48 |
49 |
50 | def camelcase_to_spaces(content):
51 | """
52 | Translate 'CamelCaseNames' to 'Camel Case Names'.
53 | Used when generating names from view classes.
54 | """
55 | camelcase_boundary = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
56 | content = re.sub(camelcase_boundary, ' \\1', content).strip()
57 | return ' '.join(content.split('_')).title()
58 |
59 |
60 | def markup_description(description):
61 | """
62 | Apply HTML markup to the given description.
63 | """
64 | if apply_markdown:
65 | description = apply_markdown(description)
66 | else:
67 | description = escape(description).replace('\n', ' ')
68 | description = '' + description + '
'
69 | return mark_safe(description)
70 |
--------------------------------------------------------------------------------
/static/rest_framework/docs/js/jquery.json-view.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jquery.json-view - jQuery collapsible JSON plugin
3 | * @version v1.0.0
4 | * @link http://github.com/bazh/jquery.json-view
5 | * @license MIT
6 | */
7 | !function(e){"use strict";var n=function(n){var a=e(" ",{"class":"collapser",on:{click:function(){var n=e(this);n.toggleClass("collapsed");var a=n.parent().children(".block"),p=a.children("ul");n.hasClass("collapsed")?(p.hide(),a.children(".dots, .comments").show()):(p.show(),a.children(".dots, .comments").hide())}}});return n&&a.addClass("collapsed"),a},a=function(a,p){var t=e.extend({},{nl2br:!0},p),r=function(e){return e.toString()?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(//g,">"):""},s=function(n,a){return e(" ",{"class":a,html:r(n)})},l=function(a,p){switch(e.type(a)){case"object":p||(p=0);var c=e(" ",{"class":"block"}),d=Object.keys(a).length;if(!d)return c.append(s("{","b")).append(" ").append(s("}","b"));c.append(s("{","b"));var i=e("",{"class":"obj collapsible level"+p});return e.each(a,function(a,t){d--;var r=e(" ").append(s('"',"q")).append(a).append(s('"',"q")).append(": ").append(l(t,p+1));-1===["object","array"].indexOf(e.type(t))||e.isEmptyObject(t)||r.prepend(n()),d>0&&r.append(","),i.append(r)}),c.append(i),c.append(s("...","dots")),c.append(s("}","b")),c.append(1===Object.keys(a).length?s("// 1 item","comments"):s("// "+Object.keys(a).length+" items","comments")),c;case"array":p||(p=0);var d=a.length,c=e(" ",{"class":"block"});if(!d)return c.append(s("[","b")).append(" ").append(s("]","b"));c.append(s("[","b"));var i=e("",{"class":"obj collapsible level"+p});return e.each(a,function(a,t){d--;var r=e(" ").append(l(t,p+1));-1===["object","array"].indexOf(e.type(t))||e.isEmptyObject(t)||r.prepend(n()),d>0&&r.append(","),i.append(r)}),c.append(i),c.append(s("...","dots")),c.append(s("]","b")),c.append(1===a.length?s("// 1 item","comments"):s("// "+a.length+" items","comments")),c;case"string":if(a=r(a),/^(http|https|file):\/\/[^\s]+$/i.test(a))return e(" ").append(s('"',"q")).append(e(" ",{href:a,text:a})).append(s('"',"q"));if(t.nl2br){var o=/\n/g;o.test(a)&&(a=(a+"").replace(o," "))}var u=e(" ",{"class":"str"}).html(a);return e(" ").append(s('"',"q")).append(u).append(s('"',"q"));case"number":return s(a.toString(),"num");case"undefined":return s("undefined","undef");case"null":return s("null","null");case"boolean":return s(a?"true":"false","bool")}};return l(a)};return e.fn.jsonView=function(n,p){var t=e(this);if(p=e.extend({},{nl2br:!0},p),"string"==typeof n)try{n=JSON.parse(n)}catch(r){}return t.append(e("
",{"class":"json-view"}).append(a(n,p))),t}}(jQuery);
--------------------------------------------------------------------------------
/templates/rest_framework/docs/index.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ document.title }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {% if code_style %}{% endif %}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {% include "rest_framework/docs/sidebar.html" %}
28 |
29 |
30 |
31 |
32 | {% include "rest_framework/docs/document.html" %}
33 |
34 |
35 |
36 |
37 | {% include "rest_framework/docs/auth/token.html" %}
38 | {% include "rest_framework/docs/auth/basic.html" %}
39 | {% include "rest_framework/docs/auth/session.html" %}
40 |
41 |
42 |
43 |
44 |
45 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/sidebar.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
45 |
--------------------------------------------------------------------------------
/utils/encoders.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper classes for parsers.
3 | """
4 | from __future__ import absolute_import, unicode_literals
5 |
6 | import datetime
7 | import decimal
8 | import json # noqa
9 | import uuid
10 |
11 | from django.db.models.query import QuerySet
12 | from django.utils import six, timezone
13 | from django.utils.encoding import force_text
14 | from django.utils.functional import Promise
15 |
16 | from rest_framework.compat import coreapi
17 |
18 |
19 | class JSONEncoder(json.JSONEncoder):
20 | """
21 | JSONEncoder subclass that knows how to encode date/time/timedelta,
22 | decimal types, generators and other basic python objects.
23 | """
24 | def default(self, obj):
25 | # For Date Time string spec, see ECMA 262
26 | # https://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
27 | if isinstance(obj, Promise):
28 | return force_text(obj)
29 | elif isinstance(obj, datetime.datetime):
30 | representation = obj.isoformat()
31 | if representation.endswith('+00:00'):
32 | representation = representation[:-6] + 'Z'
33 | return representation
34 | elif isinstance(obj, datetime.date):
35 | return obj.isoformat()
36 | elif isinstance(obj, datetime.time):
37 | if timezone and timezone.is_aware(obj):
38 | raise ValueError("JSON can't represent timezone-aware times.")
39 | representation = obj.isoformat()
40 | return representation
41 | elif isinstance(obj, datetime.timedelta):
42 | return six.text_type(obj.total_seconds())
43 | elif isinstance(obj, decimal.Decimal):
44 | # Serializers will coerce decimals to strings by default.
45 | return float(obj)
46 | elif isinstance(obj, uuid.UUID):
47 | return six.text_type(obj)
48 | elif isinstance(obj, QuerySet):
49 | return tuple(obj)
50 | elif isinstance(obj, six.binary_type):
51 | # Best-effort for binary blobs. See #4187.
52 | return obj.decode('utf-8')
53 | elif hasattr(obj, 'tolist'):
54 | # Numpy arrays and array scalars.
55 | return obj.tolist()
56 | elif (coreapi is not None) and isinstance(obj, (coreapi.Document, coreapi.Error)):
57 | raise RuntimeError(
58 | 'Cannot return a coreapi object from a JSON view. '
59 | 'You should be using a schema renderer instead for this view.'
60 | )
61 | elif hasattr(obj, '__getitem__'):
62 | try:
63 | return dict(obj)
64 | except Exception:
65 | pass
66 | elif hasattr(obj, '__iter__'):
67 | return tuple(item for item in obj)
68 | return super(JSONEncoder, self).default(obj)
69 |
--------------------------------------------------------------------------------
/utils/mediatypes.py:
--------------------------------------------------------------------------------
1 | """
2 | Handling of media types, as found in HTTP Content-Type and Accept headers.
3 |
4 | See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
5 | """
6 | from __future__ import unicode_literals
7 |
8 | from django.http.multipartparser import parse_header
9 | from django.utils.encoding import python_2_unicode_compatible
10 |
11 | from rest_framework import HTTP_HEADER_ENCODING
12 |
13 |
14 | def media_type_matches(lhs, rhs):
15 | """
16 | Returns ``True`` if the media type in the first argument <= the
17 | media type in the second argument. The media types are strings
18 | as described by the HTTP spec.
19 |
20 | Valid media type strings include:
21 |
22 | 'application/json; indent=4'
23 | 'application/json'
24 | 'text/*'
25 | '*/*'
26 | """
27 | lhs = _MediaType(lhs)
28 | rhs = _MediaType(rhs)
29 | return lhs.match(rhs)
30 |
31 |
32 | def order_by_precedence(media_type_lst):
33 | """
34 | Returns a list of sets of media type strings, ordered by precedence.
35 | Precedence is determined by how specific a media type is:
36 |
37 | 3. 'type/subtype; param=val'
38 | 2. 'type/subtype'
39 | 1. 'type/*'
40 | 0. '*/*'
41 | """
42 | ret = [set(), set(), set(), set()]
43 | for media_type in media_type_lst:
44 | precedence = _MediaType(media_type).precedence
45 | ret[3 - precedence].add(media_type)
46 | return [media_types for media_types in ret if media_types]
47 |
48 |
49 | @python_2_unicode_compatible
50 | class _MediaType(object):
51 | def __init__(self, media_type_str):
52 | self.orig = '' if (media_type_str is None) else media_type_str
53 | self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING))
54 | self.main_type, sep, self.sub_type = self.full_type.partition('/')
55 |
56 | def match(self, other):
57 | """Return true if this MediaType satisfies the given MediaType."""
58 | for key in self.params:
59 | if key != 'q' and other.params.get(key, None) != self.params.get(key, None):
60 | return False
61 |
62 | if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type:
63 | return False
64 |
65 | if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type:
66 | return False
67 |
68 | return True
69 |
70 | @property
71 | def precedence(self):
72 | """
73 | Return a precedence level from 0-3 for the media type given how specific it is.
74 | """
75 | if self.main_type == '*':
76 | return 0
77 | elif self.sub_type == '*':
78 | return 1
79 | elif not self.params or list(self.params) == ['q']:
80 | return 2
81 | return 3
82 |
83 | def __str__(self):
84 | ret = "%s/%s" % (self.main_type, self.sub_type)
85 | for key, val in self.params.items():
86 | ret += "; %s=%s" % (key, val.decode('ascii'))
87 | return ret
88 |
--------------------------------------------------------------------------------
/templates/rest_framework/login_base.html:
--------------------------------------------------------------------------------
1 | {% extends "rest_framework/base.html" %}
2 | {% load rest_framework %}
3 |
4 | {% block body %}
5 |
6 |
7 |
8 |
9 |
10 |
11 | {% block branding %}
Django REST framework {% endblock %}
12 |
13 |
14 |
15 |
61 |
62 |
63 |
64 |
65 | {% endblock %}
66 |
--------------------------------------------------------------------------------
/documentation.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import include, url
2 |
3 | from rest_framework.renderers import (
4 | CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer
5 | )
6 | from rest_framework.schemas import SchemaGenerator, get_schema_view
7 | from rest_framework.settings import api_settings
8 |
9 |
10 | def get_docs_view(
11 | title=None, description=None, schema_url=None, public=True,
12 | patterns=None, generator_class=SchemaGenerator,
13 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
14 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
15 | renderer_classes=None):
16 |
17 | if renderer_classes is None:
18 | renderer_classes = [DocumentationRenderer, CoreJSONRenderer]
19 |
20 | return get_schema_view(
21 | title=title,
22 | url=schema_url,
23 | description=description,
24 | renderer_classes=renderer_classes,
25 | public=public,
26 | patterns=patterns,
27 | generator_class=generator_class,
28 | authentication_classes=authentication_classes,
29 | permission_classes=permission_classes,
30 | )
31 |
32 |
33 | def get_schemajs_view(
34 | title=None, description=None, schema_url=None, public=True,
35 | patterns=None, generator_class=SchemaGenerator,
36 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
37 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
38 | renderer_classes = [SchemaJSRenderer]
39 |
40 | return get_schema_view(
41 | title=title,
42 | url=schema_url,
43 | description=description,
44 | renderer_classes=renderer_classes,
45 | public=public,
46 | patterns=patterns,
47 | generator_class=generator_class,
48 | authentication_classes=authentication_classes,
49 | permission_classes=permission_classes,
50 | )
51 |
52 |
53 | def include_docs_urls(
54 | title=None, description=None, schema_url=None, public=True,
55 | patterns=None, generator_class=SchemaGenerator,
56 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
57 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
58 | renderer_classes=None):
59 | docs_view = get_docs_view(
60 | title=title,
61 | description=description,
62 | schema_url=schema_url,
63 | public=public,
64 | patterns=patterns,
65 | generator_class=generator_class,
66 | authentication_classes=authentication_classes,
67 | renderer_classes=renderer_classes,
68 | permission_classes=permission_classes,
69 | )
70 | schema_js_view = get_schemajs_view(
71 | title=title,
72 | description=description,
73 | schema_url=schema_url,
74 | public=public,
75 | patterns=patterns,
76 | generator_class=generator_class,
77 | authentication_classes=authentication_classes,
78 | permission_classes=permission_classes,
79 | )
80 | urls = [
81 | url(r'^$', docs_view, name='docs-index'),
82 | url(r'^schema.js$', schema_js_view, name='schema-js')
83 | ]
84 | return include((urls, 'api-docs'), namespace='api-docs')
85 |
--------------------------------------------------------------------------------
/mixins.py:
--------------------------------------------------------------------------------
1 | """
2 | Basic building blocks for generic class based views.
3 |
4 | We don't bind behaviour to http method handlers yet,
5 | which allows mixin classes to be composed in interesting ways.
6 | """
7 | from __future__ import unicode_literals
8 |
9 | from rest_framework import status
10 | from rest_framework.response import Response
11 | from rest_framework.settings import api_settings
12 |
13 |
14 | class CreateModelMixin(object):
15 | """
16 | Create a model instance.
17 | """
18 | def create(self, request, *args, **kwargs):
19 | serializer = self.get_serializer(data=request.data)
20 | serializer.is_valid(raise_exception=True)
21 | self.perform_create(serializer)
22 | headers = self.get_success_headers(serializer.data)
23 | return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
24 |
25 | def perform_create(self, serializer):
26 | serializer.save()
27 |
28 | def get_success_headers(self, data):
29 | try:
30 | return {'Location': str(data[api_settings.URL_FIELD_NAME])}
31 | except (TypeError, KeyError):
32 | return {}
33 |
34 |
35 | class ListModelMixin(object):
36 | """
37 | List a queryset.
38 | """
39 | def list(self, request, *args, **kwargs):
40 | queryset = self.filter_queryset(self.get_queryset())
41 |
42 | page = self.paginate_queryset(queryset)
43 | if page is not None:
44 | serializer = self.get_serializer(page, many=True)
45 | return self.get_paginated_response(serializer.data)
46 |
47 | serializer = self.get_serializer(queryset, many=True)
48 | return Response(serializer.data)
49 |
50 |
51 | class RetrieveModelMixin(object):
52 | """
53 | Retrieve a model instance.
54 | """
55 | def retrieve(self, request, *args, **kwargs):
56 | instance = self.get_object()
57 | serializer = self.get_serializer(instance)
58 | return Response(serializer.data)
59 |
60 |
61 | class UpdateModelMixin(object):
62 | """
63 | Update a model instance.
64 | """
65 | def update(self, request, *args, **kwargs):
66 | partial = kwargs.pop('partial', False)
67 | instance = self.get_object()
68 | serializer = self.get_serializer(instance, data=request.data, partial=partial)
69 | serializer.is_valid(raise_exception=True)
70 | self.perform_update(serializer)
71 |
72 | if getattr(instance, '_prefetched_objects_cache', None):
73 | # If 'prefetch_related' has been applied to a queryset, we need to
74 | # forcibly invalidate the prefetch cache on the instance.
75 | instance._prefetched_objects_cache = {}
76 |
77 | return Response(serializer.data)
78 |
79 | def perform_update(self, serializer):
80 | serializer.save()
81 |
82 | def partial_update(self, request, *args, **kwargs):
83 | kwargs['partial'] = True
84 | return self.update(request, *args, **kwargs)
85 |
86 |
87 | class DestroyModelMixin(object):
88 | """
89 | Destroy a model instance.
90 | """
91 | def destroy(self, request, *args, **kwargs):
92 | instance = self.get_object()
93 | self.perform_destroy(instance)
94 | return Response(status=status.HTTP_204_NO_CONTENT)
95 |
96 | def perform_destroy(self, instance):
97 | instance.delete()
98 |
--------------------------------------------------------------------------------
/utils/representation.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper functions for creating user-friendly representations
3 | of serializer classes and serializer fields.
4 | """
5 | from __future__ import unicode_literals
6 |
7 | import re
8 |
9 | from django.db import models
10 | from django.utils.encoding import force_text
11 | from django.utils.functional import Promise
12 |
13 | from rest_framework.compat import unicode_repr
14 |
15 |
16 | def manager_repr(value):
17 | model = value.model
18 | opts = model._meta
19 | names_and_managers = [
20 | (manager.name, manager)
21 | for manager
22 | in opts.managers
23 | ]
24 | for manager_name, manager_instance in names_and_managers:
25 | if manager_instance == value:
26 | return '%s.%s.all()' % (model._meta.object_name, manager_name)
27 | return repr(value)
28 |
29 |
30 | def smart_repr(value):
31 | if isinstance(value, models.Manager):
32 | return manager_repr(value)
33 |
34 | if isinstance(value, Promise) and value._delegate_text:
35 | value = force_text(value)
36 |
37 | value = unicode_repr(value)
38 |
39 | # Representations like u'help text'
40 | # should simply be presented as 'help text'
41 | if value.startswith("u'") and value.endswith("'"):
42 | return value[1:]
43 |
44 | # Representations like
45 | #
46 | # Should be presented as
47 | #
48 | value = re.sub(' at 0x[0-9A-Fa-f]{4,32}>', '>', value)
49 |
50 | return value
51 |
52 |
53 | def field_repr(field, force_many=False):
54 | kwargs = field._kwargs
55 | if force_many:
56 | kwargs = kwargs.copy()
57 | kwargs['many'] = True
58 | kwargs.pop('child', None)
59 |
60 | arg_string = ', '.join([smart_repr(val) for val in field._args])
61 | kwarg_string = ', '.join([
62 | '%s=%s' % (key, smart_repr(val))
63 | for key, val in sorted(kwargs.items())
64 | ])
65 | if arg_string and kwarg_string:
66 | arg_string += ', '
67 |
68 | if force_many:
69 | class_name = force_many.__class__.__name__
70 | else:
71 | class_name = field.__class__.__name__
72 |
73 | return "%s(%s%s)" % (class_name, arg_string, kwarg_string)
74 |
75 |
76 | def serializer_repr(serializer, indent, force_many=None):
77 | ret = field_repr(serializer, force_many) + ':'
78 | indent_str = ' ' * indent
79 |
80 | if force_many:
81 | fields = force_many.fields
82 | else:
83 | fields = serializer.fields
84 |
85 | for field_name, field in fields.items():
86 | ret += '\n' + indent_str + field_name + ' = '
87 | if hasattr(field, 'fields'):
88 | ret += serializer_repr(field, indent + 1)
89 | elif hasattr(field, 'child'):
90 | ret += list_repr(field, indent + 1)
91 | elif hasattr(field, 'child_relation'):
92 | ret += field_repr(field.child_relation, force_many=field.child_relation)
93 | else:
94 | ret += field_repr(field)
95 |
96 | if serializer.validators:
97 | ret += '\n' + indent_str + 'class Meta:'
98 | ret += '\n' + indent_str + ' validators = ' + smart_repr(serializer.validators)
99 |
100 | return ret
101 |
102 |
103 | def list_repr(serializer, indent):
104 | child = serializer.child
105 | if hasattr(child, 'fields'):
106 | return serializer_repr(serializer, indent, force_many=child)
107 | return field_repr(serializer)
108 |
--------------------------------------------------------------------------------
/response.py:
--------------------------------------------------------------------------------
1 | """
2 | The Response class in REST framework is similar to HTTPResponse, except that
3 | it is initialized with unrendered data, instead of a pre-rendered string.
4 |
5 | The appropriate renderer is called during Django's template response rendering.
6 | """
7 | from __future__ import unicode_literals
8 |
9 | from django.template.response import SimpleTemplateResponse
10 | from django.utils import six
11 | from django.utils.six.moves.http_client import responses
12 |
13 | from rest_framework.serializers import Serializer
14 |
15 |
16 | class Response(SimpleTemplateResponse):
17 | """
18 | An HttpResponse that allows its data to be rendered into
19 | arbitrary media types.
20 | """
21 |
22 | def __init__(self, data=None, status=None,
23 | template_name=None, headers=None,
24 | exception=False, content_type=None):
25 | """
26 | Alters the init arguments slightly.
27 | For example, drop 'template_name', and instead use 'data'.
28 |
29 | Setting 'renderer' and 'media_type' will typically be deferred,
30 | For example being set automatically by the `APIView`.
31 | """
32 | super(Response, self).__init__(None, status=status)
33 |
34 | if isinstance(data, Serializer):
35 | msg = (
36 | 'You passed a Serializer instance as data, but '
37 | 'probably meant to pass serialized `.data` or '
38 | '`.error`. representation.'
39 | )
40 | raise AssertionError(msg)
41 |
42 | self.data = data
43 | self.template_name = template_name
44 | self.exception = exception
45 | self.content_type = content_type
46 |
47 | if headers:
48 | for name, value in six.iteritems(headers):
49 | self[name] = value
50 |
51 | @property
52 | def rendered_content(self):
53 | renderer = getattr(self, 'accepted_renderer', None)
54 | accepted_media_type = getattr(self, 'accepted_media_type', None)
55 | context = getattr(self, 'renderer_context', None)
56 |
57 | assert renderer, ".accepted_renderer not set on Response"
58 | assert accepted_media_type, ".accepted_media_type not set on Response"
59 | assert context is not None, ".renderer_context not set on Response"
60 | context['response'] = self
61 |
62 | media_type = renderer.media_type
63 | charset = renderer.charset
64 | content_type = self.content_type
65 |
66 | if content_type is None and charset is not None:
67 | content_type = "{0}; charset={1}".format(media_type, charset)
68 | elif content_type is None:
69 | content_type = media_type
70 | self['Content-Type'] = content_type
71 |
72 | ret = renderer.render(self.data, accepted_media_type, context)
73 | if isinstance(ret, six.text_type):
74 | assert charset, (
75 | 'renderer returned unicode, and did not specify '
76 | 'a charset value.'
77 | )
78 | return bytes(ret.encode(charset))
79 |
80 | if not ret:
81 | del self['Content-Type']
82 |
83 | return ret
84 |
85 | @property
86 | def status_text(self):
87 | """
88 | Returns reason text corresponding to our HTTP response status code.
89 | Provided for convenience.
90 | """
91 | return responses.get(self.status_code, '')
92 |
93 | def __getstate__(self):
94 | """
95 | Remove attributes from the response that shouldn't be cached.
96 | """
97 | state = super(Response, self).__getstate__()
98 | for key in (
99 | 'accepted_renderer', 'renderer_context', 'resolver_match',
100 | 'client', 'request', 'json', 'wsgi_request'
101 | ):
102 | if key in state:
103 | del state[key]
104 | state['_closable_objects'] = []
105 | return state
106 |
--------------------------------------------------------------------------------
/static/rest_framework/js/ajax-form.js:
--------------------------------------------------------------------------------
1 | function replaceDocument(docString) {
2 | var doc = document.open("text/html");
3 |
4 | doc.write(docString);
5 | doc.close();
6 | }
7 |
8 | function doAjaxSubmit(e) {
9 | var form = $(this);
10 | var btn = $(this.clk);
11 | var method = (
12 | btn.data('method') ||
13 | form.data('method') ||
14 | form.attr('method') || 'GET'
15 | ).toUpperCase();
16 |
17 | if (method === 'GET') {
18 | // GET requests can always use standard form submits.
19 | return;
20 | }
21 |
22 | var contentType =
23 | form.find('input[data-override="content-type"]').val() ||
24 | form.find('select[data-override="content-type"] option:selected').text();
25 |
26 | if (method === 'POST' && !contentType) {
27 | // POST requests can use standard form submits, unless we have
28 | // overridden the content type.
29 | return;
30 | }
31 |
32 | // At this point we need to make an AJAX form submission.
33 | e.preventDefault();
34 |
35 | var url = form.attr('action');
36 | var data;
37 |
38 | if (contentType) {
39 | data = form.find('[data-override="content"]').val() || ''
40 |
41 | if (contentType === 'multipart/form-data') {
42 | // We need to add a boundary parameter to the header
43 | // We assume the first valid-looking boundary line in the body is correct
44 | // regex is from RFC 2046 appendix A
45 | var boundaryCharNoSpace = "0-9A-Z'()+_,-./:=?";
46 | var boundaryChar = boundaryCharNoSpace + ' ';
47 | var re = new RegExp('^--([' + boundaryChar + ']{0,69}[' + boundaryCharNoSpace + '])[\\s]*?$', 'im');
48 | var boundary = data.match(re);
49 | if (boundary !== null) {
50 | contentType += '; boundary="' + boundary[1] + '"';
51 | }
52 | // Fix textarea.value EOL normalisation (multipart/form-data should use CR+NL, not NL)
53 | data = data.replace(/\n/g, '\r\n');
54 | }
55 | } else {
56 | contentType = form.attr('enctype') || form.attr('encoding')
57 |
58 | if (contentType === 'multipart/form-data') {
59 | if (!window.FormData) {
60 | alert('Your browser does not support AJAX multipart form submissions');
61 | return;
62 | }
63 |
64 | // Use the FormData API and allow the content type to be set automatically,
65 | // so it includes the boundary string.
66 | // See https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
67 | contentType = false;
68 | data = new FormData(form[0]);
69 | } else {
70 | contentType = 'application/x-www-form-urlencoded; charset=UTF-8'
71 | data = form.serialize();
72 | }
73 | }
74 |
75 | var ret = $.ajax({
76 | url: url,
77 | method: method,
78 | data: data,
79 | contentType: contentType,
80 | processData: false,
81 | headers: {
82 | 'Accept': 'text/html; q=1.0, */*'
83 | },
84 | });
85 |
86 | ret.always(function(data, textStatus, jqXHR) {
87 | if (textStatus != 'success') {
88 | jqXHR = data;
89 | }
90 |
91 | var responseContentType = jqXHR.getResponseHeader("content-type") || "";
92 |
93 | if (responseContentType.toLowerCase().indexOf('text/html') === 0) {
94 | replaceDocument(jqXHR.responseText);
95 |
96 | try {
97 | // Modify the location and scroll to top, as if after page load.
98 | history.replaceState({}, '', url);
99 | scroll(0, 0);
100 | } catch (err) {
101 | // History API not supported, so redirect.
102 | window.location = url;
103 | }
104 | } else {
105 | // Not HTML content. We can't open this directly, so redirect.
106 | window.location = url;
107 | }
108 | });
109 |
110 | return ret;
111 | }
112 |
113 | function captureSubmittingElement(e) {
114 | var target = e.target;
115 | var form = this;
116 |
117 | form.clk = target;
118 | }
119 |
120 | $.fn.ajaxForm = function() {
121 | var options = {}
122 |
123 | return this
124 | .unbind('submit.form-plugin click.form-plugin')
125 | .bind('submit.form-plugin', options, doAjaxSubmit)
126 | .bind('click.form-plugin', options, captureSubmittingElement);
127 | };
128 |
--------------------------------------------------------------------------------
/negotiation.py:
--------------------------------------------------------------------------------
1 | """
2 | Content negotiation deals with selecting an appropriate renderer given the
3 | incoming request. Typically this will be based on the request's Accept header.
4 | """
5 | from __future__ import unicode_literals
6 |
7 | from django.http import Http404
8 |
9 | from rest_framework import HTTP_HEADER_ENCODING, exceptions
10 | from rest_framework.settings import api_settings
11 | from rest_framework.utils.mediatypes import (
12 | _MediaType, media_type_matches, order_by_precedence
13 | )
14 |
15 |
16 | class BaseContentNegotiation(object):
17 | def select_parser(self, request, parsers):
18 | raise NotImplementedError('.select_parser() must be implemented')
19 |
20 | def select_renderer(self, request, renderers, format_suffix=None):
21 | raise NotImplementedError('.select_renderer() must be implemented')
22 |
23 |
24 | class DefaultContentNegotiation(BaseContentNegotiation):
25 | settings = api_settings
26 |
27 | def select_parser(self, request, parsers):
28 | """
29 | Given a list of parsers and a media type, return the appropriate
30 | parser to handle the incoming request.
31 | """
32 | for parser in parsers:
33 | if media_type_matches(parser.media_type, request.content_type):
34 | return parser
35 | return None
36 |
37 | def select_renderer(self, request, renderers, format_suffix=None):
38 | """
39 | Given a request and a list of renderers, return a two-tuple of:
40 | (renderer, media type).
41 | """
42 | # Allow URL style format override. eg. "?format=json
43 | format_query_param = self.settings.URL_FORMAT_OVERRIDE
44 | format = format_suffix or request.query_params.get(format_query_param)
45 |
46 | if format:
47 | renderers = self.filter_renderers(renderers, format)
48 |
49 | accepts = self.get_accept_list(request)
50 |
51 | # Check the acceptable media types against each renderer,
52 | # attempting more specific media types first
53 | # NB. The inner loop here isn't as bad as it first looks :)
54 | # Worst case is we're looping over len(accept_list) * len(self.renderers)
55 | for media_type_set in order_by_precedence(accepts):
56 | for renderer in renderers:
57 | for media_type in media_type_set:
58 | if media_type_matches(renderer.media_type, media_type):
59 | # Return the most specific media type as accepted.
60 | media_type_wrapper = _MediaType(media_type)
61 | if (
62 | _MediaType(renderer.media_type).precedence >
63 | media_type_wrapper.precedence
64 | ):
65 | # Eg client requests '*/*'
66 | # Accepted media type is 'application/json'
67 | full_media_type = ';'.join(
68 | (renderer.media_type,) +
69 | tuple('{0}={1}'.format(
70 | key, value.decode(HTTP_HEADER_ENCODING))
71 | for key, value in media_type_wrapper.params.items()))
72 | return renderer, full_media_type
73 | else:
74 | # Eg client requests 'application/json; indent=8'
75 | # Accepted media type is 'application/json; indent=8'
76 | return renderer, media_type
77 |
78 | raise exceptions.NotAcceptable(available_renderers=renderers)
79 |
80 | def filter_renderers(self, renderers, format):
81 | """
82 | If there is a '.json' style format suffix, filter the renderers
83 | so that we only negotiation against those that accept that format.
84 | """
85 | renderers = [renderer for renderer in renderers
86 | if renderer.format == format]
87 | if not renderers:
88 | raise Http404
89 | return renderers
90 |
91 | def get_accept_list(self, request):
92 | """
93 | Given the incoming request, return a tokenized list of media
94 | type strings.
95 | """
96 | header = request.META.get('HTTP_ACCEPT', '*/*')
97 | return [token.strip() for token in header.split(',')]
98 |
--------------------------------------------------------------------------------
/static/rest_framework/css/bootstrap-tweaks.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This CSS file contains some tweaks specific to the included Bootstrap theme.
4 | It's separate from `style.css` so that it can be easily overridden by replacing
5 | a single block in the template.
6 |
7 | */
8 |
9 | .form-actions {
10 | background: transparent;
11 | border-top-color: transparent;
12 | padding-top: 0;
13 | text-align: right;
14 | }
15 |
16 | #generic-content-form textarea {
17 | font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
18 | font-size: 80%;
19 | }
20 |
21 | .navbar-inverse .brand a {
22 | color: #999999;
23 | }
24 | .navbar-inverse .brand:hover a {
25 | color: white;
26 | text-decoration: none;
27 | }
28 |
29 | /* custom navigation styles */
30 | .navbar {
31 | width: 100%;
32 | position: fixed;
33 | left: 0;
34 | top: 0;
35 | }
36 |
37 | .navbar {
38 | background: #2C2C2C;
39 | color: white;
40 | border: none;
41 | border-top: 5px solid #A30000;
42 | border-radius: 0px;
43 | }
44 |
45 | .navbar .nav li, .navbar .nav li a, .navbar .brand:hover {
46 | color: white;
47 | }
48 |
49 | .nav-list > .active > a, .nav-list > .active > a:hover {
50 | background: #2C2C2C;
51 | }
52 |
53 | .navbar .dropdown-menu li a, .navbar .dropdown-menu li {
54 | color: #A30000;
55 | }
56 |
57 | .navbar .dropdown-menu li a:hover {
58 | background: #EEEEEE;
59 | color: #C20000;
60 | }
61 |
62 | ul.breadcrumb {
63 | margin: 70px 0 0 0;
64 | }
65 |
66 | .breadcrumb li.active a {
67 | color: #777;
68 | }
69 |
70 | .pagination>.disabled>a,
71 | .pagination>.disabled>a:hover,
72 | .pagination>.disabled>a:focus {
73 | cursor: not-allowed;
74 | pointer-events: none;
75 | }
76 |
77 | .pager>.disabled>a,
78 | .pager>.disabled>a:hover,
79 | .pager>.disabled>a:focus {
80 | pointer-events: none;
81 | }
82 |
83 | .pager .next {
84 | margin-left: 10px;
85 | }
86 |
87 | /*=== dabapps bootstrap styles ====*/
88 |
89 | html {
90 | width:100%;
91 | background: none;
92 | }
93 |
94 | /*body, .navbar .container-fluid {
95 | max-width: 1150px;
96 | margin: 0 auto;
97 | }*/
98 |
99 | body {
100 | background: url("../img/grid.png") repeat-x;
101 | background-attachment: fixed;
102 | }
103 |
104 | #content {
105 | margin: 0;
106 | padding-bottom: 60px;
107 | }
108 |
109 | /* sticky footer and footer */
110 | html, body {
111 | height: 100%;
112 | }
113 |
114 | .wrapper {
115 | position: relative;
116 | top: 0;
117 | left: 0;
118 | padding-top: 60px;
119 | margin: -60px 0;
120 | min-height: 100%;
121 | }
122 |
123 | .form-switcher {
124 | margin-bottom: 0;
125 | }
126 |
127 | .well {
128 | -webkit-box-shadow: none;
129 | -moz-box-shadow: none;
130 | box-shadow: none;
131 | }
132 |
133 | .well .form-actions {
134 | padding-bottom: 0;
135 | margin-bottom: 0;
136 | }
137 |
138 | .well form {
139 | margin-bottom: 0;
140 | }
141 |
142 | .nav-tabs {
143 | border: 0;
144 | }
145 |
146 | .nav-tabs > li {
147 | float: right;
148 | }
149 |
150 | .nav-tabs li a {
151 | margin-right: 0;
152 | }
153 |
154 | .nav-tabs > .active > a {
155 | background: #F5F5F5;
156 | }
157 |
158 | .nav-tabs > .active > a:hover {
159 | background: #F5F5F5;
160 | }
161 |
162 | .tabbable.first-tab-active .tab-content {
163 | border-top-right-radius: 0;
164 | }
165 |
166 | footer {
167 | position: absolute;
168 | bottom: 0;
169 | left: 0;
170 | clear: both;
171 | z-index: 10;
172 | height: 60px;
173 | width: 95%;
174 | margin: 0 2.5%;
175 | }
176 |
177 | footer p {
178 | text-align: center;
179 | color: gray;
180 | border-top: 1px solid #DDDDDD;
181 | padding-top: 10px;
182 | }
183 |
184 | footer a {
185 | color: gray !important;
186 | font-weight: bold;
187 | }
188 |
189 | footer a:hover {
190 | color: gray;
191 | }
192 |
193 | .page-header {
194 | border-bottom: none;
195 | padding-bottom: 0px;
196 | margin: 0;
197 | }
198 |
199 | /* custom general page styles */
200 | .hero-unit h1, .hero-unit h2 {
201 | color: #A30000;
202 | }
203 |
204 | body a {
205 | color: #A30000;
206 | }
207 |
208 | body a:hover {
209 | color: #c20000;
210 | }
211 |
212 | .request-info {
213 | clear:both;
214 | }
215 |
216 | .horizontal-checkbox label {
217 | padding-top: 0;
218 | }
219 |
220 | .horizontal-checkbox label {
221 | padding-top: 0 !important;
222 | }
223 |
224 | .horizontal-checkbox input {
225 | float: left;
226 | width: 20px;
227 | margin-top: 3px;
228 | }
229 |
230 | .modal-footer form {
231 | margin-left: 5px;
232 | margin-right: 5px;
233 | }
234 |
--------------------------------------------------------------------------------
/urlpatterns.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.conf.urls import include, url
4 |
5 | from rest_framework.compat import (
6 | URLResolver, get_regex_pattern, is_route_pattern, path, register_converter
7 | )
8 | from rest_framework.settings import api_settings
9 |
10 |
11 | def _get_format_path_converter(suffix_kwarg, allowed):
12 | if allowed:
13 | if len(allowed) == 1:
14 | allowed_pattern = allowed[0]
15 | else:
16 | allowed_pattern = '(?:%s)' % '|'.join(allowed)
17 | suffix_pattern = r"\.%s/?" % allowed_pattern
18 | else:
19 | suffix_pattern = r"\.[a-z0-9]+/?"
20 |
21 | class FormatSuffixConverter:
22 | regex = suffix_pattern
23 |
24 | def to_python(self, value):
25 | return value.strip('./')
26 |
27 | def to_url(self, value):
28 | return '.' + value + '/'
29 |
30 | converter_name = 'drf_format_suffix'
31 | if allowed:
32 | converter_name += '_' + '_'.join(allowed)
33 |
34 | return converter_name, FormatSuffixConverter
35 |
36 |
37 | def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route=None):
38 | ret = []
39 | for urlpattern in urlpatterns:
40 | if isinstance(urlpattern, URLResolver):
41 | # Set of included URL patterns
42 | regex = get_regex_pattern(urlpattern)
43 | namespace = urlpattern.namespace
44 | app_name = urlpattern.app_name
45 | kwargs = urlpattern.default_kwargs
46 | # Add in the included patterns, after applying the suffixes
47 | patterns = apply_suffix_patterns(urlpattern.url_patterns,
48 | suffix_pattern,
49 | suffix_required,
50 | suffix_route)
51 |
52 | # if the original pattern was a RoutePattern we need to preserve it
53 | if is_route_pattern(urlpattern):
54 | assert path is not None
55 | route = str(urlpattern.pattern)
56 | new_pattern = path(route, include((patterns, app_name), namespace), kwargs)
57 | else:
58 | new_pattern = url(regex, include((patterns, app_name), namespace), kwargs)
59 |
60 | ret.append(new_pattern)
61 | else:
62 | # Regular URL pattern
63 | regex = get_regex_pattern(urlpattern).rstrip('$').rstrip('/') + suffix_pattern
64 | view = urlpattern.callback
65 | kwargs = urlpattern.default_args
66 | name = urlpattern.name
67 | # Add in both the existing and the new urlpattern
68 | if not suffix_required:
69 | ret.append(urlpattern)
70 |
71 | # if the original pattern was a RoutePattern we need to preserve it
72 | if is_route_pattern(urlpattern):
73 | assert path is not None
74 | assert suffix_route is not None
75 | route = str(urlpattern.pattern).rstrip('$').rstrip('/') + suffix_route
76 | new_pattern = path(route, view, kwargs, name)
77 | else:
78 | new_pattern = url(regex, view, kwargs, name)
79 |
80 | ret.append(new_pattern)
81 |
82 | return ret
83 |
84 |
85 | def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
86 | """
87 | Supplement existing urlpatterns with corresponding patterns that also
88 | include a '.format' suffix. Retains urlpattern ordering.
89 |
90 | urlpatterns:
91 | A list of URL patterns.
92 |
93 | suffix_required:
94 | If `True`, only suffixed URLs will be generated, and non-suffixed
95 | URLs will not be used. Defaults to `False`.
96 |
97 | allowed:
98 | An optional tuple/list of allowed suffixes. eg ['json', 'api']
99 | Defaults to `None`, which allows any suffix.
100 | """
101 | suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG
102 | if allowed:
103 | if len(allowed) == 1:
104 | allowed_pattern = allowed[0]
105 | else:
106 | allowed_pattern = '(%s)' % '|'.join(allowed)
107 | suffix_pattern = r'\.(?P<%s>%s)/?$' % (suffix_kwarg, allowed_pattern)
108 | else:
109 | suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg
110 |
111 | if path and register_converter:
112 | converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed)
113 | register_converter(suffix_converter, converter_name)
114 |
115 | suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg)
116 | else:
117 | suffix_route = None
118 |
119 | return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route)
120 |
--------------------------------------------------------------------------------
/templates/rest_framework/docs/link.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
3 |
4 |
5 |
10 | Interact
11 |
12 |
13 |
{{ link.title|default:link_key }}
14 |
15 |
16 |
17 | {{ link.action|upper }}
18 | {{ link.url }}
19 |
20 |
21 |
{% render_markdown link.description %}
22 |
23 | {% if link.fields|with_location:'path' %}
24 |
Path Parameters
25 |
The following parameters should be included in the URL path.
26 |
27 |
28 | Parameter Description
29 |
30 |
31 | {% for field in link.fields|with_location:'path' %}
32 | {{ field.name }}{% if field.required %} required {% endif %}{% if field.schema.description %}{{ field.schema.description }}{% endif %}
33 | {% endfor %}
34 |
35 |
36 | {% endif %}
37 | {% if link.fields|with_location:'query' %}
38 |
Query Parameters
39 |
The following parameters should be included as part of a URL query string.
40 |
41 |
42 | Parameter Description
43 |
44 |
45 | {% for field in link.fields|with_location:'query' %}
46 | {{ field.name }}{% if field.required %} required {% endif %}{% if field.schema.description %}{{ field.schema.description }}{% endif %}
47 | {% endfor %}
48 |
49 |
50 | {% endif %}
51 | {% if link.fields|with_location:'header' %}
52 |
Header Parameters
53 |
The following parameters should be included as HTTP headers.
54 |
55 |
56 | Parameter Description
57 |
58 |
59 | {% for field in link.fields|with_location:'header' %}
60 | {{ field.name }}{% if field.required %} required {% endif %}{% if field.schema.description %}{{ field.schema.description }}{% endif %}
61 | {% endfor %}
62 |
63 |
64 | {% endif %}
65 | {% if link.fields|with_location:'body' %}
66 |
Request Body
67 |
The request body should be "{{ link.encoding }}" encoded, and should contain a single item.
68 |
69 |
70 | Parameter Description
71 |
72 |
73 | {% for field in link.fields|with_location:'body' %}
74 | {{ field.name }}{% if field.required %} required {% endif %}{% if field.schema.description %}{{ field.schema.description }}{% endif %}
75 | {% endfor %}
76 |
77 |
78 | {% elif link.fields|with_location:'form' %}
79 |
Request Body
80 |
The request body should be a "{{ link.encoding }}" encoded object, containing the following items.
81 |
82 |
83 | Parameter Description
84 |
85 |
86 | {% for field in link.fields|with_location:'form' %}
87 | {{ field.name }}{% if field.required %} required {% endif %}{% if field.schema.description %}{{ field.schema.description }}{% endif %}
88 | {% endfor %}
89 |
90 |
91 | {% endif %}
92 |
93 |
94 |
95 |
96 | {% for html in lang_htmls %}
97 | {% include html %}
98 | {% endfor %}
99 |
100 |
101 |
102 | {% include "rest_framework/docs/interact.html" with link=link %}
103 |
--------------------------------------------------------------------------------
/utils/serializer_helpers.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import collections
4 | from collections import OrderedDict
5 |
6 | from django.utils.encoding import force_text
7 |
8 | from rest_framework.compat import unicode_to_repr
9 | from rest_framework.utils import json
10 |
11 |
12 | class ReturnDict(OrderedDict):
13 | """
14 | Return object from `serializer.data` for the `Serializer` class.
15 | Includes a backlink to the serializer instance for renderers
16 | to use if they need richer field information.
17 | """
18 |
19 | def __init__(self, *args, **kwargs):
20 | self.serializer = kwargs.pop('serializer')
21 | super(ReturnDict, self).__init__(*args, **kwargs)
22 |
23 | def copy(self):
24 | return ReturnDict(self, serializer=self.serializer)
25 |
26 | def __repr__(self):
27 | return dict.__repr__(self)
28 |
29 | def __reduce__(self):
30 | # Pickling these objects will drop the .serializer backlink,
31 | # but preserve the raw data.
32 | return (dict, (dict(self),))
33 |
34 |
35 | class ReturnList(list):
36 | """
37 | Return object from `serializer.data` for the `SerializerList` class.
38 | Includes a backlink to the serializer instance for renderers
39 | to use if they need richer field information.
40 | """
41 |
42 | def __init__(self, *args, **kwargs):
43 | self.serializer = kwargs.pop('serializer')
44 | super(ReturnList, self).__init__(*args, **kwargs)
45 |
46 | def __repr__(self):
47 | return list.__repr__(self)
48 |
49 | def __reduce__(self):
50 | # Pickling these objects will drop the .serializer backlink,
51 | # but preserve the raw data.
52 | return (list, (list(self),))
53 |
54 |
55 | class BoundField(object):
56 | """
57 | A field object that also includes `.value` and `.error` properties.
58 | Returned when iterating over a serializer instance,
59 | providing an API similar to Django forms and form fields.
60 | """
61 |
62 | def __init__(self, field, value, errors, prefix=''):
63 | self._field = field
64 | self._prefix = prefix
65 | self.value = value
66 | self.errors = errors
67 | self.name = prefix + self.field_name
68 |
69 | def __getattr__(self, attr_name):
70 | return getattr(self._field, attr_name)
71 |
72 | @property
73 | def _proxy_class(self):
74 | return self._field.__class__
75 |
76 | def __repr__(self):
77 | return unicode_to_repr('<%s value=%s errors=%s>' % (
78 | self.__class__.__name__, self.value, self.errors
79 | ))
80 |
81 | def as_form_field(self):
82 | value = '' if (self.value is None or self.value is False) else self.value
83 | return self.__class__(self._field, value, self.errors, self._prefix)
84 |
85 |
86 | class JSONBoundField(BoundField):
87 | def as_form_field(self):
88 | value = self.value
89 | # When HTML form input is used and the input is not valid
90 | # value will be a JSONString, rather than a JSON primitive.
91 | if not getattr(value, 'is_json_string', False):
92 | try:
93 | value = json.dumps(self.value, sort_keys=True, indent=4)
94 | except (TypeError, ValueError):
95 | pass
96 | return self.__class__(self._field, value, self.errors, self._prefix)
97 |
98 |
99 | class NestedBoundField(BoundField):
100 | """
101 | This `BoundField` additionally implements __iter__ and __getitem__
102 | in order to support nested bound fields. This class is the type of
103 | `BoundField` that is used for serializer fields.
104 | """
105 |
106 | def __init__(self, field, value, errors, prefix=''):
107 | if value is None or value is '':
108 | value = {}
109 | super(NestedBoundField, self).__init__(field, value, errors, prefix)
110 |
111 | def __iter__(self):
112 | for field in self.fields.values():
113 | yield self[field.field_name]
114 |
115 | def __getitem__(self, key):
116 | field = self.fields[key]
117 | value = self.value.get(key) if self.value else None
118 | error = self.errors.get(key) if isinstance(self.errors, dict) else None
119 | if hasattr(field, 'fields'):
120 | return NestedBoundField(field, value, error, prefix=self.name + '.')
121 | return BoundField(field, value, error, prefix=self.name + '.')
122 |
123 | def as_form_field(self):
124 | values = {}
125 | for key, value in self.value.items():
126 | if isinstance(value, (list, dict)):
127 | values[key] = value
128 | else:
129 | values[key] = '' if (value is None or value is False) else force_text(value)
130 | return self.__class__(self._field, values, self.errors, self._prefix)
131 |
132 |
133 | class BindingDict(collections.MutableMapping):
134 | """
135 | This dict-like object is used to store fields on a serializer.
136 |
137 | This ensures that whenever fields are added to the serializer we call
138 | `field.bind()` so that the `field_name` and `parent` attributes
139 | can be set correctly.
140 | """
141 |
142 | def __init__(self, serializer):
143 | self.serializer = serializer
144 | self.fields = OrderedDict()
145 |
146 | def __setitem__(self, key, field):
147 | self.fields[key] = field
148 | field.bind(field_name=key, parent=self.serializer)
149 |
150 | def __getitem__(self, key):
151 | return self.fields[key]
152 |
153 | def __delitem__(self, key):
154 | del self.fields[key]
155 |
156 | def __iter__(self):
157 | return iter(self.fields)
158 |
159 | def __len__(self):
160 | return len(self.fields)
161 |
162 | def __repr__(self):
163 | return dict.__repr__(self.fields)
164 |
--------------------------------------------------------------------------------
/utils/model_meta.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper function for returning the field information that is associated
3 | with a model class. This includes returning all the forward and reverse
4 | relationships and their associated metadata.
5 |
6 | Usage: `get_field_info(model)` returns a `FieldInfo` instance.
7 | """
8 | from collections import OrderedDict, namedtuple
9 |
10 | FieldInfo = namedtuple('FieldResult', [
11 | 'pk', # Model field instance
12 | 'fields', # Dict of field name -> model field instance
13 | 'forward_relations', # Dict of field name -> RelationInfo
14 | 'reverse_relations', # Dict of field name -> RelationInfo
15 | 'fields_and_pk', # Shortcut for 'pk' + 'fields'
16 | 'relations' # Shortcut for 'forward_relations' + 'reverse_relations'
17 | ])
18 |
19 | RelationInfo = namedtuple('RelationInfo', [
20 | 'model_field',
21 | 'related_model',
22 | 'to_many',
23 | 'to_field',
24 | 'has_through_model',
25 | 'reverse'
26 | ])
27 |
28 |
29 | def get_field_info(model):
30 | """
31 | Given a model class, returns a `FieldInfo` instance, which is a
32 | `namedtuple`, containing metadata about the various field types on the model
33 | including information about their relationships.
34 | """
35 | opts = model._meta.concrete_model._meta
36 |
37 | pk = _get_pk(opts)
38 | fields = _get_fields(opts)
39 | forward_relations = _get_forward_relationships(opts)
40 | reverse_relations = _get_reverse_relationships(opts)
41 | fields_and_pk = _merge_fields_and_pk(pk, fields)
42 | relationships = _merge_relationships(forward_relations, reverse_relations)
43 |
44 | return FieldInfo(pk, fields, forward_relations, reverse_relations,
45 | fields_and_pk, relationships)
46 |
47 |
48 | def _get_pk(opts):
49 | pk = opts.pk
50 | rel = pk.remote_field
51 |
52 | while rel and rel.parent_link:
53 | # If model is a child via multi-table inheritance, use parent's pk.
54 | pk = pk.remote_field.model._meta.pk
55 | rel = pk.remote_field
56 |
57 | return pk
58 |
59 |
60 | def _get_fields(opts):
61 | fields = OrderedDict()
62 | for field in [field for field in opts.fields if field.serialize and not field.remote_field]:
63 | fields[field.name] = field
64 |
65 | return fields
66 |
67 |
68 | def _get_to_field(field):
69 | return getattr(field, 'to_fields', None) and field.to_fields[0]
70 |
71 |
72 | def _get_forward_relationships(opts):
73 | """
74 | Returns an `OrderedDict` of field names to `RelationInfo`.
75 | """
76 | forward_relations = OrderedDict()
77 | for field in [field for field in opts.fields if field.serialize and field.remote_field]:
78 | forward_relations[field.name] = RelationInfo(
79 | model_field=field,
80 | related_model=field.remote_field.model,
81 | to_many=False,
82 | to_field=_get_to_field(field),
83 | has_through_model=False,
84 | reverse=False
85 | )
86 |
87 | # Deal with forward many-to-many relationships.
88 | for field in [field for field in opts.many_to_many if field.serialize]:
89 | forward_relations[field.name] = RelationInfo(
90 | model_field=field,
91 | related_model=field.remote_field.model,
92 | to_many=True,
93 | # manytomany do not have to_fields
94 | to_field=None,
95 | has_through_model=(
96 | not field.remote_field.through._meta.auto_created
97 | ),
98 | reverse=False
99 | )
100 |
101 | return forward_relations
102 |
103 |
104 | def _get_reverse_relationships(opts):
105 | """
106 | Returns an `OrderedDict` of field names to `RelationInfo`.
107 | """
108 | reverse_relations = OrderedDict()
109 | all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many]
110 | for relation in all_related_objects:
111 | accessor_name = relation.get_accessor_name()
112 | reverse_relations[accessor_name] = RelationInfo(
113 | model_field=None,
114 | related_model=relation.related_model,
115 | to_many=relation.field.remote_field.multiple,
116 | to_field=_get_to_field(relation.field),
117 | has_through_model=False,
118 | reverse=True
119 | )
120 |
121 | # Deal with reverse many-to-many relationships.
122 | all_related_many_to_many_objects = [r for r in opts.related_objects if r.field.many_to_many]
123 | for relation in all_related_many_to_many_objects:
124 | accessor_name = relation.get_accessor_name()
125 | reverse_relations[accessor_name] = RelationInfo(
126 | model_field=None,
127 | related_model=relation.related_model,
128 | to_many=True,
129 | # manytomany do not have to_fields
130 | to_field=None,
131 | has_through_model=(
132 | (getattr(relation.field.remote_field, 'through', None) is not None) and
133 | not relation.field.remote_field.through._meta.auto_created
134 | ),
135 | reverse=True
136 | )
137 |
138 | return reverse_relations
139 |
140 |
141 | def _merge_fields_and_pk(pk, fields):
142 | fields_and_pk = OrderedDict()
143 | fields_and_pk['pk'] = pk
144 | fields_and_pk[pk.name] = pk
145 | fields_and_pk.update(fields)
146 |
147 | return fields_and_pk
148 |
149 |
150 | def _merge_relationships(forward_relations, reverse_relations):
151 | return OrderedDict(
152 | list(forward_relations.items()) +
153 | list(reverse_relations.items())
154 | )
155 |
156 |
157 | def is_abstract_model(model):
158 | """
159 | Given a model class, returns a boolean True if it is abstract and False if it is not.
160 | """
161 | return hasattr(model, '_meta') and hasattr(model._meta, 'abstract') and model._meta.abstract
162 |
--------------------------------------------------------------------------------
/metadata.py:
--------------------------------------------------------------------------------
1 | """
2 | The metadata API is used to allow customization of how `OPTIONS` requests
3 | are handled. We currently provide a single default implementation that returns
4 | some fairly ad-hoc information about the view.
5 |
6 | Future implementations might use JSON schema or other definitions in order
7 | to return this information in a more standardized way.
8 | """
9 | from __future__ import unicode_literals
10 |
11 | from collections import OrderedDict
12 |
13 | from django.core.exceptions import PermissionDenied
14 | from django.http import Http404
15 | from django.utils.encoding import force_text
16 |
17 | from rest_framework import exceptions, serializers
18 | from rest_framework.request import clone_request
19 | from rest_framework.utils.field_mapping import ClassLookupDict
20 |
21 |
22 | class BaseMetadata(object):
23 | def determine_metadata(self, request, view):
24 | """
25 | Return a dictionary of metadata about the view.
26 | Used to return responses for OPTIONS requests.
27 | """
28 | raise NotImplementedError(".determine_metadata() must be overridden.")
29 |
30 |
31 | class SimpleMetadata(BaseMetadata):
32 | """
33 | This is the default metadata implementation.
34 | It returns an ad-hoc set of information about the view.
35 | There are not any formalized standards for `OPTIONS` responses
36 | for us to base this on.
37 | """
38 | label_lookup = ClassLookupDict({
39 | serializers.Field: 'field',
40 | serializers.BooleanField: 'boolean',
41 | serializers.NullBooleanField: 'boolean',
42 | serializers.CharField: 'string',
43 | serializers.UUIDField: 'string',
44 | serializers.URLField: 'url',
45 | serializers.EmailField: 'email',
46 | serializers.RegexField: 'regex',
47 | serializers.SlugField: 'slug',
48 | serializers.IntegerField: 'integer',
49 | serializers.FloatField: 'float',
50 | serializers.DecimalField: 'decimal',
51 | serializers.DateField: 'date',
52 | serializers.DateTimeField: 'datetime',
53 | serializers.TimeField: 'time',
54 | serializers.ChoiceField: 'choice',
55 | serializers.MultipleChoiceField: 'multiple choice',
56 | serializers.FileField: 'file upload',
57 | serializers.ImageField: 'image upload',
58 | serializers.ListField: 'list',
59 | serializers.DictField: 'nested object',
60 | serializers.Serializer: 'nested object',
61 | })
62 |
63 | def determine_metadata(self, request, view):
64 | metadata = OrderedDict()
65 | metadata['name'] = view.get_view_name()
66 | metadata['description'] = view.get_view_description()
67 | metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
68 | metadata['parses'] = [parser.media_type for parser in view.parser_classes]
69 | if hasattr(view, 'get_serializer'):
70 | actions = self.determine_actions(request, view)
71 | if actions:
72 | metadata['actions'] = actions
73 | return metadata
74 |
75 | def determine_actions(self, request, view):
76 | """
77 | For generic class based views we return information about
78 | the fields that are accepted for 'PUT' and 'POST' methods.
79 | """
80 | actions = {}
81 | for method in {'PUT', 'POST'} & set(view.allowed_methods):
82 | view.request = clone_request(request, method)
83 | try:
84 | # Test global permissions
85 | if hasattr(view, 'check_permissions'):
86 | view.check_permissions(view.request)
87 | # Test object permissions
88 | if method == 'PUT' and hasattr(view, 'get_object'):
89 | view.get_object()
90 | except (exceptions.APIException, PermissionDenied, Http404):
91 | pass
92 | else:
93 | # If user has appropriate permissions for the view, include
94 | # appropriate metadata about the fields that should be supplied.
95 | serializer = view.get_serializer()
96 | actions[method] = self.get_serializer_info(serializer)
97 | finally:
98 | view.request = request
99 |
100 | return actions
101 |
102 | def get_serializer_info(self, serializer):
103 | """
104 | Given an instance of a serializer, return a dictionary of metadata
105 | about its fields.
106 | """
107 | if hasattr(serializer, 'child'):
108 | # If this is a `ListSerializer` then we want to examine the
109 | # underlying child serializer instance instead.
110 | serializer = serializer.child
111 | return OrderedDict([
112 | (field_name, self.get_field_info(field))
113 | for field_name, field in serializer.fields.items()
114 | if not isinstance(field, serializers.HiddenField)
115 | ])
116 |
117 | def get_field_info(self, field):
118 | """
119 | Given an instance of a serializer field, return a dictionary
120 | of metadata about it.
121 | """
122 | field_info = OrderedDict()
123 | field_info['type'] = self.label_lookup[field]
124 | field_info['required'] = getattr(field, 'required', False)
125 |
126 | attrs = [
127 | 'read_only', 'label', 'help_text',
128 | 'min_length', 'max_length',
129 | 'min_value', 'max_value'
130 | ]
131 |
132 | for attr in attrs:
133 | value = getattr(field, attr, None)
134 | if value is not None and value != '':
135 | field_info[attr] = force_text(value, strings_only=True)
136 |
137 | if getattr(field, 'child', None):
138 | field_info['child'] = self.get_field_info(field.child)
139 | elif getattr(field, 'fields', None):
140 | field_info['children'] = self.get_serializer_info(field)
141 |
142 | if (not field_info.get('read_only') and
143 | not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
144 | hasattr(field, 'choices')):
145 | field_info['choices'] = [
146 | {
147 | 'value': choice_value,
148 | 'display_name': force_text(choice_name, strings_only=True)
149 | }
150 | for choice_value, choice_name in field.choices.items()
151 | ]
152 |
153 | return field_info
154 |
--------------------------------------------------------------------------------
/decorators.py:
--------------------------------------------------------------------------------
1 | """
2 | The most important decorator in this module is `@api_view`, which is used
3 | for writing function-based views with REST framework.
4 |
5 | There are also various decorators for setting the API policies on function
6 | based views, as well as the `@detail_route` and `@list_route` decorators, which are
7 | used to annotate methods on viewsets that should be included by routers.
8 | """
9 | from __future__ import unicode_literals
10 |
11 | import types
12 | import warnings
13 |
14 | from django.utils import six
15 |
16 | from rest_framework.views import APIView
17 |
18 |
19 | def api_view(http_method_names=None, exclude_from_schema=False):
20 | """
21 | Decorator that converts a function-based view into an APIView subclass.
22 | Takes a list of allowed methods for the view as an argument.
23 | """
24 | http_method_names = ['GET'] if (http_method_names is None) else http_method_names
25 |
26 | def decorator(func):
27 |
28 | WrappedAPIView = type(
29 | six.PY3 and 'WrappedAPIView' or b'WrappedAPIView',
30 | (APIView,),
31 | {'__doc__': func.__doc__}
32 | )
33 |
34 | # Note, the above allows us to set the docstring.
35 | # It is the equivalent of:
36 | #
37 | # class WrappedAPIView(APIView):
38 | # pass
39 | # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this
40 |
41 | # api_view applied without (method_names)
42 | assert not(isinstance(http_method_names, types.FunctionType)), \
43 | '@api_view missing list of allowed HTTP methods'
44 |
45 | # api_view applied with eg. string instead of list of strings
46 | assert isinstance(http_method_names, (list, tuple)), \
47 | '@api_view expected a list of strings, received %s' % type(http_method_names).__name__
48 |
49 | allowed_methods = set(http_method_names) | {'options'}
50 | WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
51 |
52 | def handler(self, *args, **kwargs):
53 | return func(*args, **kwargs)
54 |
55 | for method in http_method_names:
56 | setattr(WrappedAPIView, method.lower(), handler)
57 |
58 | WrappedAPIView.__name__ = func.__name__
59 | WrappedAPIView.__module__ = func.__module__
60 |
61 | WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
62 | APIView.renderer_classes)
63 |
64 | WrappedAPIView.parser_classes = getattr(func, 'parser_classes',
65 | APIView.parser_classes)
66 |
67 | WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes',
68 | APIView.authentication_classes)
69 |
70 | WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes',
71 | APIView.throttle_classes)
72 |
73 | WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
74 | APIView.permission_classes)
75 |
76 | WrappedAPIView.schema = getattr(func, 'schema',
77 | APIView.schema)
78 |
79 | if exclude_from_schema:
80 | warnings.warn(
81 | "The `exclude_from_schema` argument to `api_view` is deprecated. "
82 | "Use the `schema` decorator instead, passing `None`.",
83 | DeprecationWarning
84 | )
85 | WrappedAPIView.exclude_from_schema = exclude_from_schema
86 |
87 | return WrappedAPIView.as_view()
88 | return decorator
89 |
90 |
91 | def renderer_classes(renderer_classes):
92 | def decorator(func):
93 | func.renderer_classes = renderer_classes
94 | return func
95 | return decorator
96 |
97 |
98 | def parser_classes(parser_classes):
99 | def decorator(func):
100 | func.parser_classes = parser_classes
101 | return func
102 | return decorator
103 |
104 |
105 | def authentication_classes(authentication_classes):
106 | def decorator(func):
107 | func.authentication_classes = authentication_classes
108 | return func
109 | return decorator
110 |
111 |
112 | def throttle_classes(throttle_classes):
113 | def decorator(func):
114 | func.throttle_classes = throttle_classes
115 | return func
116 | return decorator
117 |
118 |
119 | def permission_classes(permission_classes):
120 | def decorator(func):
121 | func.permission_classes = permission_classes
122 | return func
123 | return decorator
124 |
125 |
126 | def schema(view_inspector):
127 | def decorator(func):
128 | func.schema = view_inspector
129 | return func
130 | return decorator
131 |
132 |
133 | def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
134 | """
135 | Mark a ViewSet method as a routable action.
136 |
137 | Set the `detail` boolean to determine if this action should apply to
138 | instance/detail requests or collection/list requests.
139 | """
140 | methods = ['get'] if (methods is None) else methods
141 | methods = [method.lower() for method in methods]
142 |
143 | assert detail is not None, (
144 | "@action() missing required argument: 'detail'"
145 | )
146 |
147 | def decorator(func):
148 | func.bind_to_methods = methods
149 | func.detail = detail
150 | func.url_path = url_path if url_path else func.__name__
151 | func.url_name = url_name if url_name else func.__name__.replace('_', '-')
152 | func.kwargs = kwargs
153 | return func
154 | return decorator
155 |
156 |
157 | def detail_route(methods=None, **kwargs):
158 | """
159 | Used to mark a method on a ViewSet that should be routed for detail requests.
160 | """
161 | warnings.warn(
162 | "`detail_route` is pending deprecation and will be removed in 3.10 in favor of "
163 | "`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
164 | PendingDeprecationWarning, stacklevel=2
165 | )
166 |
167 | def decorator(func):
168 | func = action(methods, detail=True, **kwargs)(func)
169 | if 'url_name' not in kwargs:
170 | func.url_name = func.url_path.replace('_', '-')
171 | return func
172 | return decorator
173 |
174 |
175 | def list_route(methods=None, **kwargs):
176 | """
177 | Used to mark a method on a ViewSet that should be routed for list requests.
178 | """
179 | warnings.warn(
180 | "`list_route` is pending deprecation and will be removed in 3.10 in favor of "
181 | "`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
182 | PendingDeprecationWarning, stacklevel=2
183 | )
184 |
185 | def decorator(func):
186 | func = action(methods, detail=False, **kwargs)(func)
187 | if 'url_name' not in kwargs:
188 | func.url_name = func.url_path.replace('_', '-')
189 | return func
190 | return decorator
191 |
--------------------------------------------------------------------------------
/viewsets.py:
--------------------------------------------------------------------------------
1 | """
2 | ViewSets are essentially just a type of class based view, that doesn't provide
3 | any method handlers, such as `get()`, `post()`, etc... but instead has actions,
4 | such as `list()`, `retrieve()`, `create()`, etc...
5 |
6 | Actions are only bound to methods at the point of instantiating the views.
7 |
8 | user_list = UserViewSet.as_view({'get': 'list'})
9 | user_detail = UserViewSet.as_view({'get': 'retrieve'})
10 |
11 | Typically, rather than instantiate views from viewsets directly, you'll
12 | register the viewset with a router and let the URL conf be determined
13 | automatically.
14 |
15 | router = DefaultRouter()
16 | router.register(r'users', UserViewSet, 'user')
17 | urlpatterns = router.urls
18 | """
19 | from __future__ import unicode_literals
20 |
21 | from functools import update_wrapper
22 | from inspect import getmembers
23 |
24 | from django.utils.decorators import classonlymethod
25 | from django.views.decorators.csrf import csrf_exempt
26 |
27 | from rest_framework import generics, mixins, views
28 | from rest_framework.reverse import reverse
29 |
30 |
31 | def _is_extra_action(attr):
32 | return hasattr(attr, 'bind_to_methods')
33 |
34 |
35 | class ViewSetMixin(object):
36 | """
37 | This is the magic.
38 |
39 | Overrides `.as_view()` so that it takes an `actions` keyword that performs
40 | the binding of HTTP methods to actions on the Resource.
41 |
42 | For example, to create a concrete view binding the 'GET' and 'POST' methods
43 | to the 'list' and 'create' actions...
44 |
45 | view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
46 | """
47 |
48 | @classonlymethod
49 | def as_view(cls, actions=None, **initkwargs):
50 | """
51 | Because of the way class based views create a closure around the
52 | instantiated view, we need to totally reimplement `.as_view`,
53 | and slightly modify the view function that is created and returned.
54 | """
55 | # The suffix initkwarg is reserved for displaying the viewset type.
56 | # eg. 'List' or 'Instance'.
57 | cls.suffix = None
58 |
59 | # The detail initkwarg is reserved for introspecting the viewset type.
60 | cls.detail = None
61 |
62 | # Setting a basename allows a view to reverse its action urls. This
63 | # value is provided by the router through the initkwargs.
64 | cls.basename = None
65 |
66 | # actions must not be empty
67 | if not actions:
68 | raise TypeError("The `actions` argument must be provided when "
69 | "calling `.as_view()` on a ViewSet. For example "
70 | "`.as_view({'get': 'list'})`")
71 |
72 | # sanitize keyword arguments
73 | for key in initkwargs:
74 | if key in cls.http_method_names:
75 | raise TypeError("You tried to pass in the %s method name as a "
76 | "keyword argument to %s(). Don't do that."
77 | % (key, cls.__name__))
78 | if not hasattr(cls, key):
79 | raise TypeError("%s() received an invalid keyword %r" % (
80 | cls.__name__, key))
81 |
82 | def view(request, *args, **kwargs):
83 | self = cls(**initkwargs)
84 | # We also store the mapping of request methods to actions,
85 | # so that we can later set the action attribute.
86 | # eg. `self.action = 'list'` on an incoming GET request.
87 | self.action_map = actions
88 |
89 | # Bind methods to actions
90 | # This is the bit that's different to a standard view
91 | for method, action in actions.items():
92 | handler = getattr(self, action)
93 | setattr(self, method, handler)
94 |
95 | if hasattr(self, 'get') and not hasattr(self, 'head'):
96 | self.head = self.get
97 |
98 | self.request = request
99 | self.args = args
100 | self.kwargs = kwargs
101 |
102 | # And continue as usual
103 | return self.dispatch(request, *args, **kwargs)
104 |
105 | # take name and docstring from class
106 | update_wrapper(view, cls, updated=())
107 |
108 | # and possible attributes set by decorators
109 | # like csrf_exempt from dispatch
110 | update_wrapper(view, cls.dispatch, assigned=())
111 |
112 | # We need to set these on the view function, so that breadcrumb
113 | # generation can pick out these bits of information from a
114 | # resolved URL.
115 | view.cls = cls
116 | view.initkwargs = initkwargs
117 | view.suffix = initkwargs.get('suffix', None)
118 | view.actions = actions
119 | return csrf_exempt(view)
120 |
121 | def initialize_request(self, request, *args, **kwargs):
122 | """
123 | Set the `.action` attribute on the view, depending on the request method.
124 | """
125 | request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs)
126 | method = request.method.lower()
127 | if method == 'options':
128 | # This is a special case as we always provide handling for the
129 | # options method in the base `View` class.
130 | # Unlike the other explicitly defined actions, 'metadata' is implicit.
131 | self.action = 'metadata'
132 | else:
133 | self.action = self.action_map.get(method)
134 | return request
135 |
136 | def reverse_action(self, url_name, *args, **kwargs):
137 | """
138 | Reverse the action for the given `url_name`.
139 | """
140 | url_name = '%s-%s' % (self.basename, url_name)
141 | kwargs.setdefault('request', self.request)
142 |
143 | return reverse(url_name, *args, **kwargs)
144 |
145 | @classmethod
146 | def get_extra_actions(cls):
147 | """
148 | Get the methods that are marked as an extra ViewSet `@action`.
149 | """
150 | return [method for _, method in getmembers(cls, _is_extra_action)]
151 |
152 |
153 | class ViewSet(ViewSetMixin, views.APIView):
154 | """
155 | The base ViewSet class does not provide any actions by default.
156 | """
157 | pass
158 |
159 |
160 | class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
161 | """
162 | The GenericViewSet class does not provide any actions by default,
163 | but does include the base set of generic view behavior, such as
164 | the `get_object` and `get_queryset` methods.
165 | """
166 | pass
167 |
168 |
169 | class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
170 | mixins.ListModelMixin,
171 | GenericViewSet):
172 | """
173 | A viewset that provides default `list()` and `retrieve()` actions.
174 | """
175 | pass
176 |
177 |
178 | class ModelViewSet(mixins.CreateModelMixin,
179 | mixins.RetrieveModelMixin,
180 | mixins.UpdateModelMixin,
181 | mixins.DestroyModelMixin,
182 | mixins.ListModelMixin,
183 | GenericViewSet):
184 | """
185 | A viewset that provides default `create()`, `retrieve()`, `update()`,
186 | `partial_update()`, `destroy()` and `list()` actions.
187 | """
188 | pass
189 |
--------------------------------------------------------------------------------
/permissions.py:
--------------------------------------------------------------------------------
1 | """
2 | Provides a set of pluggable permission policies.
3 | """
4 | from __future__ import unicode_literals
5 |
6 | from django.http import Http404
7 |
8 | from rest_framework import exceptions
9 |
10 | SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
11 |
12 |
13 | class BasePermission(object):
14 | """
15 | A base class from which all permission classes should inherit.
16 | """
17 |
18 | def has_permission(self, request, view):
19 | """
20 | Return `True` if permission is granted, `False` otherwise.
21 | """
22 | return True
23 |
24 | def has_object_permission(self, request, view, obj):
25 | """
26 | Return `True` if permission is granted, `False` otherwise.
27 | """
28 | return True
29 |
30 |
31 | class AllowAny(BasePermission):
32 | """
33 | Allow any access.
34 | This isn't strictly required, since you could use an empty
35 | permission_classes list, but it's useful because it makes the intention
36 | more explicit.
37 | """
38 |
39 | def has_permission(self, request, view):
40 | return True
41 |
42 |
43 | class IsAuthenticated(BasePermission):
44 | """
45 | Allows access only to authenticated users.
46 | """
47 |
48 | def has_permission(self, request, view):
49 | return request.user and request.user.is_authenticated
50 |
51 |
52 | class IsAdminUser(BasePermission):
53 | """
54 | Allows access only to admin users.
55 | """
56 |
57 | def has_permission(self, request, view):
58 | return request.user and request.user.is_staff
59 |
60 |
61 | class IsAuthenticatedOrReadOnly(BasePermission):
62 | """
63 | The request is authenticated as a user, or is a read-only request.
64 | """
65 |
66 | def has_permission(self, request, view):
67 | return (
68 | request.method in SAFE_METHODS or
69 | request.user and
70 | request.user.is_authenticated
71 | )
72 |
73 |
74 | class DjangoModelPermissions(BasePermission):
75 | """
76 | The request is authenticated using `django.contrib.auth` permissions.
77 | See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
78 |
79 | It ensures that the user is authenticated, and has the appropriate
80 | `add`/`change`/`delete` permissions on the model.
81 |
82 | This permission can only be applied against view classes that
83 | provide a `.queryset` attribute.
84 | """
85 |
86 | # Map methods into required permission codes.
87 | # Override this if you need to also provide 'view' permissions,
88 | # or if you want to provide custom permission codes.
89 | perms_map = {
90 | 'GET': [],
91 | 'OPTIONS': [],
92 | 'HEAD': [],
93 | 'POST': ['%(app_label)s.add_%(model_name)s'],
94 | 'PUT': ['%(app_label)s.change_%(model_name)s'],
95 | 'PATCH': ['%(app_label)s.change_%(model_name)s'],
96 | 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
97 | }
98 |
99 | authenticated_users_only = True
100 |
101 | def get_required_permissions(self, method, model_cls):
102 | """
103 | Given a model and an HTTP method, return the list of permission
104 | codes that the user is required to have.
105 | """
106 | kwargs = {
107 | 'app_label': model_cls._meta.app_label,
108 | 'model_name': model_cls._meta.model_name
109 | }
110 |
111 | if method not in self.perms_map:
112 | raise exceptions.MethodNotAllowed(method)
113 |
114 | return [perm % kwargs for perm in self.perms_map[method]]
115 |
116 | def _queryset(self, view):
117 | assert hasattr(view, 'get_queryset') \
118 | or getattr(view, 'queryset', None) is not None, (
119 | 'Cannot apply {} on a view that does not set '
120 | '`.queryset` or have a `.get_queryset()` method.'
121 | ).format(self.__class__.__name__)
122 |
123 | if hasattr(view, 'get_queryset'):
124 | queryset = view.get_queryset()
125 | assert queryset is not None, (
126 | '{}.get_queryset() returned None'.format(view.__class__.__name__)
127 | )
128 | return queryset
129 | return view.queryset
130 |
131 | def has_permission(self, request, view):
132 | # Workaround to ensure DjangoModelPermissions are not applied
133 | # to the root view when using DefaultRouter.
134 | if getattr(view, '_ignore_model_permissions', False):
135 | return True
136 |
137 | if not request.user or (
138 | not request.user.is_authenticated and self.authenticated_users_only):
139 | return False
140 |
141 | queryset = self._queryset(view)
142 | perms = self.get_required_permissions(request.method, queryset.model)
143 |
144 | return request.user.has_perms(perms)
145 |
146 |
147 | class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
148 | """
149 | Similar to DjangoModelPermissions, except that anonymous users are
150 | allowed read-only access.
151 | """
152 | authenticated_users_only = False
153 |
154 |
155 | class DjangoObjectPermissions(DjangoModelPermissions):
156 | """
157 | The request is authenticated using Django's object-level permissions.
158 | It requires an object-permissions-enabled backend, such as Django Guardian.
159 |
160 | It ensures that the user is authenticated, and has the appropriate
161 | `add`/`change`/`delete` permissions on the object using .has_perms.
162 |
163 | This permission can only be applied against view classes that
164 | provide a `.queryset` attribute.
165 | """
166 | perms_map = {
167 | 'GET': [],
168 | 'OPTIONS': [],
169 | 'HEAD': [],
170 | 'POST': ['%(app_label)s.add_%(model_name)s'],
171 | 'PUT': ['%(app_label)s.change_%(model_name)s'],
172 | 'PATCH': ['%(app_label)s.change_%(model_name)s'],
173 | 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
174 | }
175 |
176 | def get_required_object_permissions(self, method, model_cls):
177 | kwargs = {
178 | 'app_label': model_cls._meta.app_label,
179 | 'model_name': model_cls._meta.model_name
180 | }
181 |
182 | if method not in self.perms_map:
183 | raise exceptions.MethodNotAllowed(method)
184 |
185 | return [perm % kwargs for perm in self.perms_map[method]]
186 |
187 | def has_object_permission(self, request, view, obj):
188 | # authentication checks have already executed via has_permission
189 | queryset = self._queryset(view)
190 | model_cls = queryset.model
191 | user = request.user
192 |
193 | perms = self.get_required_object_permissions(request.method, model_cls)
194 |
195 | if not user.has_perms(perms, obj):
196 | # If the user does not have permissions we need to determine if
197 | # they have read permissions to see 403, or not, and simply see
198 | # a 404 response.
199 |
200 | if request.method in SAFE_METHODS:
201 | # Read permissions already checked and failed, no need
202 | # to make another lookup.
203 | raise Http404
204 |
205 | read_perms = self.get_required_object_permissions('GET', model_cls)
206 | if not user.has_perms(read_perms, obj):
207 | raise Http404
208 |
209 | # Has read permissions.
210 | return False
211 |
212 | return True
213 |
--------------------------------------------------------------------------------
/versioning.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | import re
5 |
6 | from django.utils.translation import ugettext_lazy as _
7 |
8 | from rest_framework import exceptions
9 | from rest_framework.compat import unicode_http_header
10 | from rest_framework.reverse import _reverse
11 | from rest_framework.settings import api_settings
12 | from rest_framework.templatetags.rest_framework import replace_query_param
13 | from rest_framework.utils.mediatypes import _MediaType
14 |
15 |
16 | class BaseVersioning(object):
17 | default_version = api_settings.DEFAULT_VERSION
18 | allowed_versions = api_settings.ALLOWED_VERSIONS
19 | version_param = api_settings.VERSION_PARAM
20 |
21 | def determine_version(self, request, *args, **kwargs):
22 | msg = '{cls}.determine_version() must be implemented.'
23 | raise NotImplementedError(msg.format(
24 | cls=self.__class__.__name__
25 | ))
26 |
27 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
28 | return _reverse(viewname, args, kwargs, request, format, **extra)
29 |
30 | def is_allowed_version(self, version):
31 | if not self.allowed_versions:
32 | return True
33 | return ((version is not None and version == self.default_version) or
34 | (version in self.allowed_versions))
35 |
36 |
37 | class AcceptHeaderVersioning(BaseVersioning):
38 | """
39 | GET /something/ HTTP/1.1
40 | Host: example.com
41 | Accept: application/json; version=1.0
42 | """
43 | invalid_version_message = _('Invalid version in "Accept" header.')
44 |
45 | def determine_version(self, request, *args, **kwargs):
46 | media_type = _MediaType(request.accepted_media_type)
47 | version = media_type.params.get(self.version_param, self.default_version)
48 | version = unicode_http_header(version)
49 | if not self.is_allowed_version(version):
50 | raise exceptions.NotAcceptable(self.invalid_version_message)
51 | return version
52 |
53 | # We don't need to implement `reverse`, as the versioning is based
54 | # on the `Accept` header, not on the request URL.
55 |
56 |
57 | class URLPathVersioning(BaseVersioning):
58 | """
59 | To the client this is the same style as `NamespaceVersioning`.
60 | The difference is in the backend - this implementation uses
61 | Django's URL keyword arguments to determine the version.
62 |
63 | An example URL conf for two views that accept two different versions.
64 |
65 | urlpatterns = [
66 | url(r'^(?P[v1|v2]+)/users/$', users_list, name='users-list'),
67 | url(r'^(?P[v1|v2]+)/users/(?P[0-9]+)/$', users_detail, name='users-detail')
68 | ]
69 |
70 | GET /1.0/something/ HTTP/1.1
71 | Host: example.com
72 | Accept: application/json
73 | """
74 | invalid_version_message = _('Invalid version in URL path.')
75 |
76 | def determine_version(self, request, *args, **kwargs):
77 | version = kwargs.get(self.version_param, self.default_version)
78 | if not self.is_allowed_version(version):
79 | raise exceptions.NotFound(self.invalid_version_message)
80 | return version
81 |
82 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
83 | if request.version is not None:
84 | kwargs = {} if (kwargs is None) else kwargs
85 | kwargs[self.version_param] = request.version
86 |
87 | return super(URLPathVersioning, self).reverse(
88 | viewname, args, kwargs, request, format, **extra
89 | )
90 |
91 |
92 | class NamespaceVersioning(BaseVersioning):
93 | """
94 | To the client this is the same style as `URLPathVersioning`.
95 | The difference is in the backend - this implementation uses
96 | Django's URL namespaces to determine the version.
97 |
98 | An example URL conf that is namespaced into two separate versions
99 |
100 | # users/urls.py
101 | urlpatterns = [
102 | url(r'^/users/$', users_list, name='users-list'),
103 | url(r'^/users/(?P[0-9]+)/$', users_detail, name='users-detail')
104 | ]
105 |
106 | # urls.py
107 | urlpatterns = [
108 | url(r'^v1/', include('users.urls', namespace='v1')),
109 | url(r'^v2/', include('users.urls', namespace='v2'))
110 | ]
111 |
112 | GET /1.0/something/ HTTP/1.1
113 | Host: example.com
114 | Accept: application/json
115 | """
116 | invalid_version_message = _('Invalid version in URL path. Does not match any version namespace.')
117 |
118 | def determine_version(self, request, *args, **kwargs):
119 | resolver_match = getattr(request, 'resolver_match', None)
120 | if resolver_match is None or not resolver_match.namespace:
121 | return self.default_version
122 |
123 | # Allow for possibly nested namespaces.
124 | possible_versions = resolver_match.namespace.split(':')
125 | for version in possible_versions:
126 | if self.is_allowed_version(version):
127 | return version
128 | raise exceptions.NotFound(self.invalid_version_message)
129 |
130 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
131 | if request.version is not None:
132 | viewname = self.get_versioned_viewname(viewname, request)
133 | return super(NamespaceVersioning, self).reverse(
134 | viewname, args, kwargs, request, format, **extra
135 | )
136 |
137 | def get_versioned_viewname(self, viewname, request):
138 | return request.version + ':' + viewname
139 |
140 |
141 | class HostNameVersioning(BaseVersioning):
142 | """
143 | GET /something/ HTTP/1.1
144 | Host: v1.example.com
145 | Accept: application/json
146 | """
147 | hostname_regex = re.compile(r'^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$')
148 | invalid_version_message = _('Invalid version in hostname.')
149 |
150 | def determine_version(self, request, *args, **kwargs):
151 | hostname, separator, port = request.get_host().partition(':')
152 | match = self.hostname_regex.match(hostname)
153 | if not match:
154 | return self.default_version
155 | version = match.group(1)
156 | if not self.is_allowed_version(version):
157 | raise exceptions.NotFound(self.invalid_version_message)
158 | return version
159 |
160 | # We don't need to implement `reverse`, as the hostname will already be
161 | # preserved as part of the REST framework `reverse` implementation.
162 |
163 |
164 | class QueryParameterVersioning(BaseVersioning):
165 | """
166 | GET /something/?version=0.1 HTTP/1.1
167 | Host: example.com
168 | Accept: application/json
169 | """
170 | invalid_version_message = _('Invalid version in query parameter.')
171 |
172 | def determine_version(self, request, *args, **kwargs):
173 | version = request.query_params.get(self.version_param, self.default_version)
174 | print(version)
175 | if not self.is_allowed_version(version):
176 | raise exceptions.NotFound(self.invalid_version_message)
177 | return version
178 |
179 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
180 | url = super(QueryParameterVersioning, self).reverse(
181 | viewname, args, kwargs, request, format, **extra
182 | )
183 | if request.version is not None:
184 | return replace_query_param(url, self.version_param, request.version)
185 | return url
186 |
--------------------------------------------------------------------------------