├── tests
├── __init__.py
├── browsable_api
│ ├── __init__.py
│ ├── no_auth_urls.py
│ ├── auth_urls.py
│ ├── views.py
│ └── test_browsable_api.py
├── urls.py
├── test_settings.py
├── description.py
├── test_write_only_fields.py
├── test_serializer_nested.py
├── test_status.py
├── test_middleware.py
├── test_viewsets.py
├── test_reverse.py
├── test_negotiation.py
├── conftest.py
├── models.py
├── utils.py
├── test_multitable_inheritance.py
├── test_templatetags.py
├── test_bound_fields.py
├── test_urlpatterns.py
├── test_relations_generic.py
├── test_parsers.py
├── test_description.py
└── test_serializer_bulk_update.py
├── rest_framework
├── utils
│ ├── __init__.py
│ ├── urls.py
│ ├── humanize_datetime.py
│ ├── formatting.py
│ ├── breadcrumbs.py
│ ├── html.py
│ ├── encoders.py
│ ├── mediatypes.py
│ ├── representation.py
│ └── serializer_helpers.py
├── authtoken
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── south_migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── admin.py
│ ├── views.py
│ ├── serializers.py
│ └── models.py
├── templatetags
│ └── __init__.py
├── models.py
├── templates
│ └── rest_framework
│ │ ├── inline
│ │ ├── list_fieldset.html
│ │ ├── fieldset.html
│ │ ├── checkbox.html
│ │ ├── textarea.html
│ │ ├── form.html
│ │ ├── input.html
│ │ ├── checkbox_multiple.html
│ │ ├── select.html
│ │ ├── select_multiple.html
│ │ └── radio.html
│ │ ├── api.html
│ │ ├── login.html
│ │ ├── vertical
│ │ ├── list_fieldset.html
│ │ ├── fieldset.html
│ │ ├── form.html
│ │ ├── checkbox.html
│ │ ├── textarea.html
│ │ ├── input.html
│ │ ├── select.html
│ │ ├── select_multiple.html
│ │ ├── checkbox_multiple.html
│ │ └── radio.html
│ │ ├── api_form.html
│ │ ├── raw_data_form.html
│ │ ├── pagination
│ │ ├── previous_and_next.html
│ │ └── numbers.html
│ │ ├── horizontal
│ │ ├── fieldset.html
│ │ ├── form.html
│ │ ├── list_fieldset.html
│ │ ├── checkbox.html
│ │ ├── textarea.html
│ │ ├── input.html
│ │ ├── select.html
│ │ ├── select_multiple.html
│ │ ├── checkbox_multiple.html
│ │ └── radio.html
│ │ └── login_base.html
├── locale
│ ├── ar
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── cs
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── da
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── de
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── en
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── es
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── et
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── fr
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── hu
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── id
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── it
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── mk
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── nl
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── pl
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── ru
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── sk
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── sv
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── tr
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── uk
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── vi
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── en_US
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── ko_KR
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── pt_BR
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── pt_PT
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ └── zh_CN
│ │ └── LC_MESSAGES
│ │ └── django.mo
├── static
│ └── rest_framework
│ │ ├── img
│ │ ├── grid.png
│ │ ├── glyphicons-halflings.png
│ │ └── glyphicons-halflings-white.png
│ │ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ │ ├── css
│ │ ├── prettify.css
│ │ ├── default.css
│ │ └── bootstrap-tweaks.css
│ │ └── js
│ │ └── default.js
├── __init__.py
├── urls.py
├── reverse.py
├── status.py
├── urlpatterns.py
├── mixins.py
├── response.py
└── negotiation.py
├── setup.cfg
├── docs
├── CNAME
├── img
│ ├── logo.png
│ ├── apiary.png
│ ├── slate.png
│ ├── cerulean.png
│ ├── quickstart.png
│ ├── sponsors
│ │ ├── 2-sga.png
│ │ ├── 3-aba.png
│ │ ├── 3-isl.png
│ │ ├── 1-cyan.png
│ │ ├── 1-divio.png
│ │ ├── 1-lulu.png
│ │ ├── 2-byte.png
│ │ ├── 2-crate.png
│ │ ├── 2-hipo.png
│ │ ├── 2-vinta.png
│ │ ├── 3-blimp.png
│ │ ├── 3-garfo.png
│ │ ├── 3-holvi.png
│ │ ├── 3-tivix.png
│ │ ├── 1-potato.png
│ │ ├── 1-runscope.png
│ │ ├── 2-compile.png
│ │ ├── 2-cryptico.png
│ │ ├── 2-django.png
│ │ ├── 2-heroku.png
│ │ ├── 2-hipflask.png
│ │ ├── 2-laterpay.png
│ │ ├── 2-nexthub.png
│ │ ├── 2-opbeat.png
│ │ ├── 2-rapasso.png
│ │ ├── 2-sirono.png
│ │ ├── 2-wusawork.png
│ │ ├── 3-aditium.png
│ │ ├── 3-beefarm.png
│ │ ├── 3-cantemo.gif
│ │ ├── 3-gizmag.png
│ │ ├── 3-nephila.png
│ │ ├── 3-openeye.png
│ │ ├── 3-phurba.png
│ │ ├── 3-pkgfarm.png
│ │ ├── 3-safari.png
│ │ ├── 3-shippo.png
│ │ ├── 3-teonite.png
│ │ ├── 3-vzzual.png
│ │ ├── 3-wildfish.png
│ │ ├── 0-eventbrite.png
│ │ ├── 1-kuwaitnet.png
│ │ ├── 1-purplebit.png
│ │ ├── 1-wiredrive.png
│ │ ├── 2-prorenata.png
│ │ ├── 2-pulsecode.png
│ │ ├── 3-alwaysdata.png
│ │ ├── 3-brightloop.png
│ │ ├── 3-fluxility.png
│ │ ├── 3-ipushpull.png
│ │ ├── 3-makespace.png
│ │ ├── 3-pathwright.png
│ │ ├── 3-providenz.png
│ │ ├── 3-trackmaven.png
│ │ ├── 3-transcode.png
│ │ ├── 1-simple-energy.png
│ │ ├── 2-koordinates.png
│ │ ├── 2-singing-horse.png
│ │ ├── 3-ax_semantics.png
│ │ ├── 3-infinite_code.png
│ │ ├── 3-life_the_game.png
│ │ ├── 2-lightning_kite.png
│ │ ├── 2-mirus_research.png
│ │ ├── 2-schuberg_philis.png
│ │ ├── 3-thermondo-gmbh.png
│ │ ├── 1-vokal_interactive.png
│ │ ├── 2-rheinwerk_verlag.png
│ │ ├── 2-security_compass.png
│ │ ├── 3-crosswordtracker.png
│ │ ├── 3-triggered_messaging.png
│ │ └── 3-imt_computer_services.png
│ ├── travis-status.png
│ ├── pages-pagination.png
│ ├── self-describing.png
│ ├── cursor-pagination.png
│ ├── django-rest-swagger.png
│ ├── rest-framework-docs.png
│ ├── labels-and-milestones.png
│ └── link-header-pagination.png
├── topics
│ ├── writable-nested-serializers.md
│ ├── ajax-csrf-cors.md
│ ├── browser-enhancements.md
│ └── rest-hypermedia-hateoas.md
└── api-guide
│ ├── reverse.md
│ ├── format-suffixes.md
│ └── metadata.md
├── requirements
├── requirements-documentation.txt
├── requirements-testing.txt
├── requirements-codestyle.txt
├── requirements-optionals.txt
└── requirements-packaging.txt
├── docs_theme
├── img
│ ├── grid.png
│ ├── favicon.ico
│ ├── glyphicons-halflings.png
│ └── glyphicons-halflings-white.png
├── js
│ └── theme.js
├── css
│ └── prettify.css
└── nav.html
├── .gitignore
├── MANIFEST.in
├── .tx
└── config
├── requirements.txt
├── tox.ini
├── .travis.yml
├── LICENSE.md
├── runtests.py
├── setup.py
└── mkdocs.yml
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rest_framework/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/browsable_api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rest_framework/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = 1
3 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | www.django-rest-framework.org
2 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/south_migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rest_framework/models.py:
--------------------------------------------------------------------------------
1 | # Just to keep things like ./manage.py test happy
2 |
--------------------------------------------------------------------------------
/docs/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/logo.png
--------------------------------------------------------------------------------
/requirements/requirements-documentation.txt:
--------------------------------------------------------------------------------
1 | # MkDocs to build our documentation.
2 | mkdocs==0.11.1
3 |
--------------------------------------------------------------------------------
/docs/img/apiary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/apiary.png
--------------------------------------------------------------------------------
/docs/img/slate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/slate.png
--------------------------------------------------------------------------------
/docs/img/cerulean.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/cerulean.png
--------------------------------------------------------------------------------
/docs/img/quickstart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/quickstart.png
--------------------------------------------------------------------------------
/docs_theme/img/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs_theme/img/grid.png
--------------------------------------------------------------------------------
/requirements/requirements-testing.txt:
--------------------------------------------------------------------------------
1 | # PyTest for running the tests.
2 | pytest==2.6.4
3 | pytest-django==2.8.0
4 |
--------------------------------------------------------------------------------
/docs/img/sponsors/2-sga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-sga.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-aba.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-aba.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-isl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-isl.png
--------------------------------------------------------------------------------
/docs/img/travis-status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/travis-status.png
--------------------------------------------------------------------------------
/docs_theme/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs_theme/img/favicon.ico
--------------------------------------------------------------------------------
/docs_theme/js/theme.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 |
3 | $('pre code').parent().addClass('prettyprint well');
4 |
5 | });
6 |
--------------------------------------------------------------------------------
/docs/img/pages-pagination.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/pages-pagination.png
--------------------------------------------------------------------------------
/docs/img/self-describing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/self-describing.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-cyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-cyan.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-divio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-divio.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-lulu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-lulu.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-byte.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-byte.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-crate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-crate.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-hipo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-hipo.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-vinta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-vinta.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-blimp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-blimp.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-garfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-garfo.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-holvi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-holvi.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-tivix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-tivix.png
--------------------------------------------------------------------------------
/requirements/requirements-codestyle.txt:
--------------------------------------------------------------------------------
1 | # PEP8 code linting, which we run on all commits.
2 | flake8==2.4.0
3 | pep8==1.5.7
4 |
--------------------------------------------------------------------------------
/docs/img/cursor-pagination.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/cursor-pagination.png
--------------------------------------------------------------------------------
/docs/img/django-rest-swagger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/django-rest-swagger.png
--------------------------------------------------------------------------------
/docs/img/rest-framework-docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/rest-framework-docs.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-potato.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-potato.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-runscope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-runscope.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-compile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-compile.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-cryptico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-cryptico.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-django.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-django.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-heroku.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-heroku.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-hipflask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-hipflask.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-laterpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-laterpay.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-nexthub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-nexthub.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-opbeat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-opbeat.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-rapasso.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-rapasso.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-sirono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-sirono.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-wusawork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-wusawork.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-aditium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-aditium.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-beefarm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-beefarm.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-cantemo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-cantemo.gif
--------------------------------------------------------------------------------
/docs/img/sponsors/3-gizmag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-gizmag.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-nephila.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-nephila.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-openeye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-openeye.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-phurba.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-phurba.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-pkgfarm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-pkgfarm.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-safari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-safari.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-shippo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-shippo.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-teonite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-teonite.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-vzzual.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-vzzual.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-wildfish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-wildfish.png
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/list_fieldset.html:
--------------------------------------------------------------------------------
1 | Lists are not currently supported in HTML input.
2 |
--------------------------------------------------------------------------------
/docs/img/labels-and-milestones.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/labels-and-milestones.png
--------------------------------------------------------------------------------
/docs/img/sponsors/0-eventbrite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/0-eventbrite.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-kuwaitnet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-kuwaitnet.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-purplebit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-purplebit.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-wiredrive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-wiredrive.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-prorenata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-prorenata.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-pulsecode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-pulsecode.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-alwaysdata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-alwaysdata.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-brightloop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-brightloop.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-fluxility.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-fluxility.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-ipushpull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-ipushpull.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-makespace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-makespace.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-pathwright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-pathwright.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-providenz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-providenz.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-trackmaven.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-trackmaven.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-transcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-transcode.png
--------------------------------------------------------------------------------
/docs/img/link-header-pagination.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/link-header-pagination.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-simple-energy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-simple-energy.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-koordinates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-koordinates.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-singing-horse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-singing-horse.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-ax_semantics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-ax_semantics.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-infinite_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-infinite_code.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-life_the_game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-life_the_game.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-lightning_kite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-lightning_kite.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-mirus_research.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-mirus_research.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-schuberg_philis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-schuberg_philis.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-thermondo-gmbh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-thermondo-gmbh.png
--------------------------------------------------------------------------------
/docs_theme/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs_theme/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/docs/img/sponsors/1-vokal_interactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/1-vokal_interactive.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-rheinwerk_verlag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-rheinwerk_verlag.png
--------------------------------------------------------------------------------
/docs/img/sponsors/2-security_compass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/2-security_compass.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-crosswordtracker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-crosswordtracker.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-triggered_messaging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-triggered_messaging.png
--------------------------------------------------------------------------------
/docs/img/sponsors/3-imt_computer_services.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs/img/sponsors/3-imt_computer_services.png
--------------------------------------------------------------------------------
/docs_theme/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/docs_theme/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/rest_framework/locale/ar/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/ar/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/cs/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/cs/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/da/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/da/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/de/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/de/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/en/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/en/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/es/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/es/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/et/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/et/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/fr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/fr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/hu/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/hu/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/id/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/id/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/it/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/it/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/mk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/mk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/nl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/nl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/pl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/pl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/ru/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/sk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/sk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/sv/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/sv/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/tr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/tr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/uk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/uk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/vi/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/vi/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/tests/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | Blank URLConf just to keep the test suite happy
3 | """
4 | from django.conf.urls import patterns
5 |
6 | urlpatterns = patterns('')
7 |
--------------------------------------------------------------------------------
/rest_framework/locale/en_US/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/pt_PT/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/pt_PT/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/img/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/static/rest_framework/img/grid.png
--------------------------------------------------------------------------------
/requirements/requirements-optionals.txt:
--------------------------------------------------------------------------------
1 | # Optional packages which may be used with REST framework.
2 | markdown==2.5.2
3 | django-guardian==1.2.5
4 | django-filter==0.9.2
5 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/api.html:
--------------------------------------------------------------------------------
1 | {% extends "rest_framework/base.html" %}
2 |
3 | {# Override this template in your own templates directory to customize #}
4 |
--------------------------------------------------------------------------------
/rest_framework/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 |
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/static/rest_framework/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.db
3 | *~
4 | .*
5 |
6 | /site/
7 | /htmlcov/
8 | /coverage/
9 | /build/
10 | /dist/
11 | /*.egg-info/
12 | /env/
13 | MANIFEST
14 |
15 | !.gitignore
16 | !.travis.yml
17 |
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/static/rest_framework/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/static/rest_framework/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/static/rest_framework/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/django-rest-framework/master/rest_framework/static/rest_framework/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/requirements/requirements-packaging.txt:
--------------------------------------------------------------------------------
1 | # Wheel for PyPI installs.
2 | wheel==0.24.0
3 |
4 | # Twine for secured PyPI uploads.
5 | twine==1.4.0
6 |
7 | # Transifex client for managing translation resources.
8 | transifex-client==0.10
9 |
--------------------------------------------------------------------------------
/tests/browsable_api/no_auth_urls.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.conf.urls import patterns
3 |
4 | from .views import MockView
5 |
6 | urlpatterns = patterns(
7 | '',
8 | (r'^$', MockView.as_view()),
9 | )
10 |
--------------------------------------------------------------------------------
/rest_framework/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 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE.md
3 | recursive-include rest_framework/static *.js *.css *.png *.eot *.svg *.ttf *.woff
4 | recursive-include rest_framework/templates *.html
5 | recursive-exclude * __pycache__
6 | recursive-exclude * *.py[co]
7 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/list_fieldset.html:
--------------------------------------------------------------------------------
1 |
/LC_MESSAGES/django.po
6 | source_file = rest_framework/locale/en_US/LC_MESSAGES/django.po
7 | source_lang = en_US
8 | type = PO
9 |
10 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/api_form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% csrf_token %}
3 | {% for field in form %}
4 | {% if not field.read_only %}
5 | {% render_field field style=style %}
6 | {% endif %}
7 | {% endfor %}
8 |
9 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from rest_framework.authtoken.models import Token
3 |
4 |
5 | class TokenAdmin(admin.ModelAdmin):
6 | list_display = ('key', 'user', 'created')
7 | fields = ('user',)
8 | ordering = ('-created',)
9 |
10 |
11 | admin.site.register(Token, TokenAdmin)
12 |
--------------------------------------------------------------------------------
/tests/browsable_api/auth_urls.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.conf.urls import patterns, url, include
3 |
4 | from .views import MockView
5 |
6 |
7 | urlpatterns = patterns(
8 | '',
9 | (r'^$', MockView.as_view()),
10 | url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
11 | )
12 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/checkbox.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
10 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/textarea.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {% endif %}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
12 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
12 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/raw_data_form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 | {% csrf_token %}
3 | {{ form.non_field_errors }}
4 | {% for field in form %}
5 |
12 | {% endfor %}
13 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/input.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {% endif %}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/pagination/previous_and_next.html:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/tests/browsable_api/views.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from rest_framework.views import APIView
4 | from rest_framework import authentication
5 | from rest_framework import renderers
6 | from rest_framework.response import Response
7 |
8 |
9 | class MockView(APIView):
10 |
11 | authentication_classes = (authentication.SessionAuthentication,)
12 | renderer_classes = (renderers.BrowsableAPIRenderer,)
13 |
14 | def get(self, request):
15 | return Response({'a': 1, 'b': 2, 'c': 3})
16 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/checkbox_multiple.html:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
14 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/form.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
16 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/list_fieldset.html:
--------------------------------------------------------------------------------
1 | {% load rest_framework %}
2 |
17 |
--------------------------------------------------------------------------------
/tests/test_settings.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.test import TestCase
3 | from rest_framework.settings import APISettings
4 |
5 |
6 | class TestSettings(TestCase):
7 | def test_import_error_message_maintained(self):
8 | """
9 | Make sure import errors are captured and raised sensibly.
10 | """
11 | settings = APISettings({
12 | 'DEFAULT_RENDERER_CLASSES': [
13 | 'tests.invalid_module.InvalidClassName'
14 | ]
15 | })
16 | with self.assertRaises(ImportError):
17 | settings.DEFAULT_RENDERER_CLASSES
18 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # The base set of requirements for REST framework is actually
2 | # just Django, but for the purposes of development and testing
3 | # there are a number of packages that it is useful to install.
4 |
5 | # Laying these out as seperate requirements files, allows us to
6 | # only included the relevent sets when running tox, and ensures
7 | # we are only ever declaring out dependancies in one place.
8 |
9 | -r requirements/requirements-optionals.txt
10 | -r requirements/requirements-testing.txt
11 | -r requirements/requirements-documentation.txt
12 | -r requirements/requirements-codestyle.txt
13 | -r requirements/requirements-packaging.txt
14 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/checkbox.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/select.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {% endif %}
5 |
13 |
14 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/select_multiple.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "No items to select." as no_items %}
3 |
4 |
5 | {% if field.label %}
6 |
7 | {% endif %}
8 |
15 |
16 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/checkbox.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/textarea.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {% endif %}
5 |
6 | {% if field.errors %}
7 | {% for error in field.errors %}{{ error }}{% endfor %}
8 | {% endif %}
9 | {% if field.help_text %}
10 | {{ field.help_text }}
11 | {% endif %}
12 |
13 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/input.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {% endif %}
5 |
6 | {% if field.errors %}
7 | {% for error in field.errors %}{{ error }}{% endfor %}
8 | {% endif %}
9 | {% if field.help_text %}
10 | {{ field.help_text }}
11 | {% endif %}
12 |
13 |
--------------------------------------------------------------------------------
/rest_framework/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | ______ _____ _____ _____ __
3 | | ___ \ ___/ ___|_ _| / _| | |
4 | | |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__
5 | | /| __| `--. \ | | | _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ /
6 | | |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | <
7 | \_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_|
8 | """
9 |
10 | __title__ = 'Django REST framework'
11 | __version__ = '3.1.1'
12 | __author__ = 'Tom Christie'
13 | __license__ = 'BSD 2-Clause'
14 | __copyright__ = 'Copyright 2011-2015 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 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | from django.conf import settings
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')),
21 | ],
22 | options={
23 | },
24 | bases=(models.Model,),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/textarea.html:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/rest_framework/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 = patterns('',
8 | ...
9 | url(r'^auth/', include('rest_framework.urls', namespace='rest_framework'))
10 | )
11 |
12 | The urls must be namespaced as 'rest_framework', and you should make sure
13 | your authentication settings include `SessionAuthentication`.
14 | """
15 | from __future__ import unicode_literals
16 | from django.conf.urls import patterns, url
17 | from django.contrib.auth import views
18 |
19 |
20 | template_name = {'template_name': 'rest_framework/login.html'}
21 |
22 | urlpatterns = patterns(
23 | '',
24 | url(r'^login/$', views.login, template_name, name='login'),
25 | url(r'^logout/$', views.logout, template_name, name='logout')
26 | )
27 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/input.html:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/tests/description.py:
--------------------------------------------------------------------------------
1 | # -- coding: utf-8 --
2 |
3 | # Apparently there is a python 2.6 issue where docstrings of imported view classes
4 | # do not retain their encoding information even if a module has a proper
5 | # encoding declaration at the top of its source file. Therefore for tests
6 | # to catch unicode related errors, a mock view has to be declared in a separate
7 | # module.
8 |
9 | from rest_framework.views import APIView
10 |
11 |
12 | # test strings snatched from http://www.columbia.edu/~fdc/utf8/,
13 | # http://winrus.com/utf8-jap.htm and memory
14 | UTF8_TEST_DOCSTRING = (
15 | 'zażółć gęślą jaźń'
16 | 'Sîne klâwen durh die wolken sint geslagen'
17 | 'Τη γλώσσα μου έδωσαν ελληνική'
18 | 'யாமறிந்த மொழிகளிலே தமிழ்மொழி'
19 | 'На берегу пустынных волн'
20 | 'てすと'
21 | 'アイウエオカキクケコサシスセソタチツテ'
22 | )
23 |
24 |
25 | class ViewWithNonASCIICharactersInDocstring(APIView):
26 | __doc__ = UTF8_TEST_DOCSTRING
27 |
--------------------------------------------------------------------------------
/docs_theme/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 | }
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/inline/radio.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "None" as none_choice %}
3 |
4 |
25 |
--------------------------------------------------------------------------------
/rest_framework/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 | }
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/select.html:
--------------------------------------------------------------------------------
1 |
2 | {% if field.label %}
3 |
4 | {% endif %}
5 |
13 | {% if field.errors %}
14 | {% for error in field.errors %}{{ error }}{% endfor %}
15 | {% endif %}
16 | {% if field.help_text %}
17 | {{ field.help_text }}
18 | {% endif %}
19 |
20 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework.views import APIView
2 | from rest_framework import parsers
3 | from rest_framework import renderers
4 | from rest_framework.response import Response
5 | from rest_framework.authtoken.models import Token
6 | from rest_framework.authtoken.serializers import AuthTokenSerializer
7 |
8 |
9 | class ObtainAuthToken(APIView):
10 | throttle_classes = ()
11 | permission_classes = ()
12 | parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
13 | renderer_classes = (renderers.JSONRenderer,)
14 | serializer_class = AuthTokenSerializer
15 |
16 | def post(self, request):
17 | serializer = self.serializer_class(data=request.data)
18 | serializer.is_valid(raise_exception=True)
19 | user = serializer.validated_data['user']
20 | token, created = Token.objects.get_or_create(user=user)
21 | return Response({'token': token.key})
22 |
23 |
24 | obtain_auth_token = ObtainAuthToken.as_view()
25 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/select_multiple.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "No items to select." as no_items %}
3 |
4 |
5 | {% if field.label %}
6 |
7 | {% endif %}
8 |
15 | {% if field.errors %}
16 | {% for error in field.errors %}{{ error }}{% endfor %}
17 | {% endif %}
18 | {% if field.help_text %}
19 | {{ field.help_text }}
20 | {% endif %}
21 |
22 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/select.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/select_multiple.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "No items to select." as no_items %}
3 |
4 |
24 |
--------------------------------------------------------------------------------
/rest_framework/utils/urls.py:
--------------------------------------------------------------------------------
1 | from django.utils.six.moves.urllib import parse as urlparse
2 |
3 |
4 | def replace_query_param(url, key, val):
5 | """
6 | Given a URL and a key/val pair, set or replace an item in the query
7 | parameters of the URL, and return the new URL.
8 | """
9 | (scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
10 | query_dict = urlparse.parse_qs(query)
11 | query_dict[key] = [val]
12 | query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
13 | return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
14 |
15 |
16 | def remove_query_param(url, key):
17 | """
18 | Given a URL and a key/val pair, remove an item in the query
19 | parameters of the URL, and return the new URL.
20 | """
21 | (scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
22 | query_dict = urlparse.parse_qs(query)
23 | query_dict.pop(key, None)
24 | query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
25 | return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
26 |
--------------------------------------------------------------------------------
/tests/test_write_only_fields.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from rest_framework import serializers
3 |
4 |
5 | class WriteOnlyFieldTests(TestCase):
6 | def setUp(self):
7 | class ExampleSerializer(serializers.Serializer):
8 | email = serializers.EmailField()
9 | password = serializers.CharField(write_only=True)
10 |
11 | def create(self, attrs):
12 | return attrs
13 |
14 | self.Serializer = ExampleSerializer
15 |
16 | def write_only_fields_are_present_on_input(self):
17 | data = {
18 | 'email': 'foo@example.com',
19 | 'password': '123'
20 | }
21 | serializer = self.Serializer(data=data)
22 | self.assertTrue(serializer.is_valid())
23 | self.assertEquals(serializer.validated_data, data)
24 |
25 | def write_only_fields_are_not_present_on_output(self):
26 | instance = {
27 | 'email': 'foo@example.com',
28 | 'password': '123'
29 | }
30 | serializer = self.Serializer(instance)
31 | self.assertEquals(serializer.data, {'email': 'foo@example.com'})
32 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import authenticate
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 | from rest_framework import exceptions, serializers
5 |
6 |
7 | class AuthTokenSerializer(serializers.Serializer):
8 | username = serializers.CharField()
9 | password = serializers.CharField(style={'input_type': 'password'})
10 |
11 | def validate(self, attrs):
12 | username = attrs.get('username')
13 | password = attrs.get('password')
14 |
15 | if username and password:
16 | user = authenticate(username=username, password=password)
17 |
18 | if user:
19 | if not user.is_active:
20 | msg = _('User account is disabled.')
21 | raise exceptions.ValidationError(msg)
22 | else:
23 | msg = _('Unable to log in with provided credentials.')
24 | raise exceptions.ValidationError(msg)
25 | else:
26 | msg = _('Must include "username" and "password".')
27 | raise exceptions.ValidationError(msg)
28 |
29 | attrs['user'] = user
30 | return attrs
31 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | py27-{flake8,docs},
4 | {py26,py27}-django14,
5 | {py26,py27,py32,py33,py34}-django{15,16},
6 | {py27,py32,py33,py34}-django{17,18,master}
7 |
8 | [testenv]
9 | commands = ./runtests.py --fast
10 | setenv =
11 | PYTHONDONTWRITEBYTECODE=1
12 | deps =
13 | django14: Django==1.4.11 # Should track minimum supported
14 | django15: Django==1.5.6 # Should track minimum supported
15 | django16: Django==1.6.3 # Should track minimum supported
16 | django17: Django==1.7.2 # Should track maximum supported
17 | django18: Django==1.8 # Should track maximum supported
18 | djangomaster: https://github.com/django/django/archive/master.tar.gz
19 | -rrequirements/requirements-testing.txt
20 | -rrequirements/requirements-optionals.txt
21 |
22 | [testenv:py27-flake8]
23 | deps =
24 | -rrequirements/requirements-codestyle.txt
25 | -rrequirements/requirements-testing.txt
26 | commands = ./runtests.py --lintonly
27 |
28 | [testenv:py27-docs]
29 | deps =
30 | -rrequirements/requirements-testing.txt
31 | -rrequirements/requirements-documentation.txt
32 | commands = mkdocs build
33 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | sudo: false
4 |
5 | env:
6 | - TOX_ENV=py27-flake8
7 | - TOX_ENV=py27-docs
8 | - TOX_ENV=py34-django18
9 | - TOX_ENV=py33-django18
10 | - TOX_ENV=py32-django18
11 | - TOX_ENV=py27-django18
12 | - TOX_ENV=py34-django17
13 | - TOX_ENV=py33-django17
14 | - TOX_ENV=py32-django17
15 | - TOX_ENV=py27-django17
16 | - TOX_ENV=py34-django16
17 | - TOX_ENV=py33-django16
18 | - TOX_ENV=py32-django16
19 | - TOX_ENV=py27-django16
20 | - TOX_ENV=py26-django16
21 | - TOX_ENV=py34-django15
22 | - TOX_ENV=py33-django15
23 | - TOX_ENV=py32-django15
24 | - TOX_ENV=py27-django15
25 | - TOX_ENV=py26-django15
26 | - TOX_ENV=py27-django14
27 | - TOX_ENV=py26-django14
28 | - TOX_ENV=py27-djangomaster
29 | - TOX_ENV=py32-djangomaster
30 | - TOX_ENV=py33-djangomaster
31 | - TOX_ENV=py34-djangomaster
32 |
33 | matrix:
34 | fast_finish: true
35 | allow_failures:
36 | - env: TOX_ENV=py27-djangomaster
37 | - env: TOX_ENV=py32-djangomaster
38 | - env: TOX_ENV=py33-djangomaster
39 | - env: TOX_ENV=py34-djangomaster
40 |
41 | install:
42 | - pip install tox
43 |
44 | script:
45 | - tox -e $TOX_ENV
46 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/pagination/numbers.html:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/css/default.css:
--------------------------------------------------------------------------------
1 |
2 | /* The navbar is fixed at >= 980px wide, so add padding to the body to prevent
3 | content running up underneath it. */
4 |
5 | h1 {
6 | font-weight: 500;
7 | }
8 |
9 | h2, h3 {
10 | font-weight: 300;
11 | }
12 |
13 | .resource-description, .response-info {
14 | margin-bottom: 2em;
15 | }
16 |
17 | .version:before {
18 | content: "v";
19 | opacity: 0.6;
20 | padding-right: 0.25em;
21 | }
22 |
23 | .version {
24 | font-size: 70%;
25 | }
26 |
27 | .format-option {
28 | font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
29 | }
30 |
31 | .button-form {
32 | float: right;
33 | margin-right: 1em;
34 | }
35 |
36 | form select, form input, form textarea {
37 | width: 90%;
38 | }
39 |
40 | form select[multiple] {
41 | height: 150px;
42 | }
43 |
44 | /* To allow tooltips to work on disabled elements */
45 | .disabled-tooltip-shield {
46 | position: absolute;
47 | top: 0;
48 | right: 0;
49 | bottom: 0;
50 | left: 0;
51 | }
52 |
53 | .errorlist {
54 | margin-top: 0.5em;
55 | }
56 |
57 | pre {
58 | overflow: auto;
59 | word-wrap: normal;
60 | white-space: pre;
61 | font-size: 12px;
62 | }
63 |
64 | .page-header {
65 | border-bottom: none;
66 | padding-bottom: 0px;
67 | }
68 |
--------------------------------------------------------------------------------
/tests/test_serializer_nested.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 |
4 | class TestNestedSerializer:
5 | def setup(self):
6 | class NestedSerializer(serializers.Serializer):
7 | one = serializers.IntegerField(max_value=10)
8 | two = serializers.IntegerField(max_value=10)
9 |
10 | class TestSerializer(serializers.Serializer):
11 | nested = NestedSerializer()
12 |
13 | self.Serializer = TestSerializer
14 |
15 | def test_nested_validate(self):
16 | input_data = {
17 | 'nested': {
18 | 'one': '1',
19 | 'two': '2',
20 | }
21 | }
22 | expected_data = {
23 | 'nested': {
24 | 'one': 1,
25 | 'two': 2,
26 | }
27 | }
28 | serializer = self.Serializer(data=input_data)
29 | assert serializer.is_valid()
30 | assert serializer.validated_data == expected_data
31 |
32 | def test_nested_serialize_empty(self):
33 | expected_data = {
34 | 'nested': {
35 | 'one': None,
36 | 'two': None
37 | }
38 | }
39 | serializer = self.Serializer()
40 | assert serializer.data == expected_data
41 |
--------------------------------------------------------------------------------
/tests/test_status.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.test import TestCase
3 | from rest_framework.status import (
4 | is_informational, is_success, is_redirect, is_client_error, is_server_error
5 | )
6 |
7 |
8 | class TestStatus(TestCase):
9 | def test_status_categories(self):
10 | self.assertFalse(is_informational(99))
11 | self.assertTrue(is_informational(100))
12 | self.assertTrue(is_informational(199))
13 | self.assertFalse(is_informational(200))
14 |
15 | self.assertFalse(is_success(199))
16 | self.assertTrue(is_success(200))
17 | self.assertTrue(is_success(299))
18 | self.assertFalse(is_success(300))
19 |
20 | self.assertFalse(is_redirect(299))
21 | self.assertTrue(is_redirect(300))
22 | self.assertTrue(is_redirect(399))
23 | self.assertFalse(is_redirect(400))
24 |
25 | self.assertFalse(is_client_error(399))
26 | self.assertTrue(is_client_error(400))
27 | self.assertTrue(is_client_error(499))
28 | self.assertFalse(is_client_error(500))
29 |
30 | self.assertFalse(is_server_error(499))
31 | self.assertTrue(is_server_error(500))
32 | self.assertTrue(is_server_error(599))
33 | self.assertFalse(is_server_error(600))
34 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/checkbox_multiple.html:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/tests/test_middleware.py:
--------------------------------------------------------------------------------
1 |
2 | from django.conf.urls import patterns, url
3 | from django.contrib.auth.models import User
4 | from rest_framework.authentication import TokenAuthentication
5 | from rest_framework.authtoken.models import Token
6 | from rest_framework.test import APITestCase
7 | from rest_framework.views import APIView
8 |
9 |
10 | urlpatterns = patterns(
11 | '',
12 | url(r'^$', APIView.as_view(authentication_classes=(TokenAuthentication,))),
13 | )
14 |
15 |
16 | class MyMiddleware(object):
17 |
18 | def process_response(self, request, response):
19 | assert hasattr(request, 'user'), '`user` is not set on request'
20 | assert request.user.is_authenticated(), '`user` is not authenticated'
21 | return response
22 |
23 |
24 | class TestMiddleware(APITestCase):
25 |
26 | urls = 'tests.test_middleware'
27 |
28 | def test_middleware_can_access_user_when_processing_response(self):
29 | user = User.objects.create_user('john', 'john@example.com', 'password')
30 | key = 'abcd1234'
31 | Token.objects.create(key=key, user=user)
32 |
33 | with self.settings(
34 | MIDDLEWARE_CLASSES=('tests.test_middleware.MyMiddleware',)
35 | ):
36 | auth = 'Token ' + key
37 | self.client.get('/', HTTP_AUTHORIZATION=auth)
38 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/checkbox_multiple.html:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | Copyright (c) 2011-2015, Tom Christie
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 | Redistributions in binary form must reproduce the above copyright notice, this
12 | list of conditions and the following disclaimer in the documentation and/or
13 | other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/tests/test_viewsets.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from rest_framework import status
3 | from rest_framework.response import Response
4 | from rest_framework.test import APIRequestFactory
5 | from rest_framework.viewsets import GenericViewSet
6 |
7 |
8 | factory = APIRequestFactory()
9 |
10 |
11 | class BasicViewSet(GenericViewSet):
12 | def list(self, request, *args, **kwargs):
13 | return Response({'ACTION': 'LIST'})
14 |
15 |
16 | class InitializeViewSetsTestCase(TestCase):
17 | def test_initialize_view_set_with_actions(self):
18 | request = factory.get('/', '', content_type='application/json')
19 | my_view = BasicViewSet.as_view(actions={
20 | 'get': 'list',
21 | })
22 |
23 | response = my_view(request)
24 | self.assertEqual(response.status_code, status.HTTP_200_OK)
25 | self.assertEqual(response.data, {'ACTION': 'LIST'})
26 |
27 | def test_initialize_view_set_with_empty_actions(self):
28 | try:
29 | BasicViewSet.as_view()
30 | except TypeError as e:
31 | self.assertEqual(str(e), "The `actions` argument must be provided "
32 | "when calling `.as_view()` on a ViewSet. "
33 | "For example `.as_view({'get': 'list'})`")
34 | else:
35 | self.fail("actions must not be empty.")
36 |
--------------------------------------------------------------------------------
/rest_framework/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 |
--------------------------------------------------------------------------------
/rest_framework/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 |
8 |
9 | # Prior to Django 1.5, the AUTH_USER_MODEL setting does not exist.
10 | # Note that we don't perform this code in the compat module due to
11 | # bug report #1297
12 | # See: https://github.com/tomchristie/django-rest-framework/issues/1297
13 | AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
14 |
15 |
16 | @python_2_unicode_compatible
17 | class Token(models.Model):
18 | """
19 | The default authorization token model.
20 | """
21 | key = models.CharField(max_length=40, primary_key=True)
22 | user = models.OneToOneField(AUTH_USER_MODEL, related_name='auth_token')
23 | created = models.DateTimeField(auto_now_add=True)
24 |
25 | class Meta:
26 | # Work around for a bug in Django:
27 | # https://code.djangoproject.com/ticket/19422
28 | #
29 | # Also see corresponding ticket:
30 | # https://github.com/tomchristie/django-rest-framework/issues/705
31 | abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
32 |
33 | def save(self, *args, **kwargs):
34 | if not self.key:
35 | self.key = self.generate_key()
36 | return super(Token, self).save(*args, **kwargs)
37 |
38 | def generate_key(self):
39 | return binascii.hexlify(os.urandom(20)).decode()
40 |
41 | def __str__(self):
42 | return self.key
43 |
--------------------------------------------------------------------------------
/rest_framework/reverse.py:
--------------------------------------------------------------------------------
1 | """
2 | Provide urlresolver functions that return fully qualified URLs or view names
3 | """
4 | from __future__ import unicode_literals
5 | from django.core.urlresolvers import reverse as django_reverse
6 | from django.core.urlresolvers import NoReverseMatch
7 | from django.utils import six
8 | from django.utils.functional import lazy
9 |
10 |
11 | def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
12 | """
13 | If versioning is being used then we pass any `reverse` calls through
14 | to the versioning scheme instance, so that the resulting URL
15 | can be modified if needed.
16 | """
17 | scheme = getattr(request, 'versioning_scheme', None)
18 | if scheme is not None:
19 | try:
20 | return scheme.reverse(viewname, args, kwargs, request, format, **extra)
21 | except NoReverseMatch:
22 | # In case the versioning scheme reversal fails, fallback to the
23 | # default implementation
24 | pass
25 |
26 | return _reverse(viewname, args, kwargs, request, format, **extra)
27 |
28 |
29 | def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
30 | """
31 | Same as `django.core.urlresolvers.reverse`, but optionally takes a request
32 | and returns a fully qualified URL, using the request to get the base URL.
33 | """
34 | if format is not None:
35 | kwargs = kwargs or {}
36 | kwargs['format'] = format
37 | url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
38 | if request:
39 | return request.build_absolute_uri(url)
40 | return url
41 |
42 |
43 | reverse_lazy = lazy(reverse, six.text_type)
44 |
--------------------------------------------------------------------------------
/tests/test_reverse.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.conf.urls import patterns, url
3 | from django.core.urlresolvers import NoReverseMatch
4 | from django.test import TestCase
5 | from rest_framework.reverse import reverse
6 | from rest_framework.test import APIRequestFactory
7 |
8 | factory = APIRequestFactory()
9 |
10 |
11 | def null_view(request):
12 | pass
13 |
14 | urlpatterns = patterns(
15 | '',
16 | url(r'^view$', null_view, name='view'),
17 | )
18 |
19 |
20 | class MockVersioningScheme(object):
21 |
22 | def __init__(self, raise_error=False):
23 | self.raise_error = raise_error
24 |
25 | def reverse(self, *args, **kwargs):
26 | if self.raise_error:
27 | raise NoReverseMatch()
28 |
29 | return 'http://scheme-reversed/view'
30 |
31 |
32 | class ReverseTests(TestCase):
33 | """
34 | Tests for fully qualified URLs when using `reverse`.
35 | """
36 | urls = 'tests.test_reverse'
37 |
38 | def test_reversed_urls_are_fully_qualified(self):
39 | request = factory.get('/view')
40 | url = reverse('view', request=request)
41 | self.assertEqual(url, 'http://testserver/view')
42 |
43 | def test_reverse_with_versioning_scheme(self):
44 | request = factory.get('/view')
45 | request.versioning_scheme = MockVersioningScheme()
46 |
47 | url = reverse('view', request=request)
48 | self.assertEqual(url, 'http://scheme-reversed/view')
49 |
50 | def test_reverse_with_versioning_scheme_fallback_to_default_on_error(self):
51 | request = factory.get('/view')
52 | request.versioning_scheme = MockVersioningScheme(raise_error=True)
53 |
54 | url = reverse('view', request=request)
55 | self.assertEqual(url, 'http://testserver/view')
56 |
--------------------------------------------------------------------------------
/tests/test_negotiation.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.test import TestCase
3 | from rest_framework.negotiation import DefaultContentNegotiation
4 | from rest_framework.request import Request
5 | from rest_framework.renderers import BaseRenderer
6 | from rest_framework.test import APIRequestFactory
7 |
8 |
9 | factory = APIRequestFactory()
10 |
11 |
12 | class MockJSONRenderer(BaseRenderer):
13 | media_type = 'application/json'
14 |
15 |
16 | class MockHTMLRenderer(BaseRenderer):
17 | media_type = 'text/html'
18 |
19 |
20 | class NoCharsetSpecifiedRenderer(BaseRenderer):
21 | media_type = 'my/media'
22 |
23 |
24 | class TestAcceptedMediaType(TestCase):
25 | def setUp(self):
26 | self.renderers = [MockJSONRenderer(), MockHTMLRenderer()]
27 | self.negotiator = DefaultContentNegotiation()
28 |
29 | def select_renderer(self, request):
30 | return self.negotiator.select_renderer(request, self.renderers)
31 |
32 | def test_client_without_accept_use_renderer(self):
33 | request = Request(factory.get('/'))
34 | accepted_renderer, accepted_media_type = self.select_renderer(request)
35 | self.assertEqual(accepted_media_type, 'application/json')
36 |
37 | def test_client_underspecifies_accept_use_renderer(self):
38 | request = Request(factory.get('/', HTTP_ACCEPT='*/*'))
39 | accepted_renderer, accepted_media_type = self.select_renderer(request)
40 | self.assertEqual(accepted_media_type, 'application/json')
41 |
42 | def test_client_overspecifies_accept_use_client(self):
43 | request = Request(factory.get('/', HTTP_ACCEPT='application/json; indent=8'))
44 | accepted_renderer, accepted_media_type = self.select_renderer(request)
45 | self.assertEqual(accepted_media_type, 'application/json; indent=8')
46 |
--------------------------------------------------------------------------------
/docs/topics/writable-nested-serializers.md:
--------------------------------------------------------------------------------
1 | > To save HTTP requests, it may be convenient to send related documents along with the request.
2 | >
3 | > — [JSON API specification for Ember Data][cite].
4 |
5 | # Writable nested serializers
6 |
7 | Although flat data structures serve to properly delineate between the individual entities in your service, there are cases where it may be more appropriate or convenient to use nested data structures.
8 |
9 | Nested data structures are easy enough to work with if they're read-only - simply nest your serializer classes and you're good to go. However, there are a few more subtleties to using writable nested serializers, due to the dependencies between the various model instances, and the need to save or delete multiple instances in a single action.
10 |
11 | ## One-to-many data structures
12 |
13 | *Example of a **read-only** nested serializer. Nothing complex to worry about here.*
14 |
15 | class ToDoItemSerializer(serializers.ModelSerializer):
16 | class Meta:
17 | model = ToDoItem
18 | fields = ('text', 'is_completed')
19 |
20 | class ToDoListSerializer(serializers.ModelSerializer):
21 | items = ToDoItemSerializer(many=True, read_only=True)
22 |
23 | class Meta:
24 | model = ToDoList
25 | fields = ('title', 'items')
26 |
27 | Some example output from our serializer.
28 |
29 | {
30 | 'title': 'Leaving party preperations',
31 | 'items': [
32 | {'text': 'Compile playlist', 'is_completed': True},
33 | {'text': 'Send invites', 'is_completed': False},
34 | {'text': 'Clean house', 'is_completed': False}
35 | ]
36 | }
37 |
38 | Let's take a look at updating our nested one-to-many data structure.
39 |
40 | ### Validation errors
41 |
42 | ### Adding and removing items
43 |
44 | ### Making PATCH requests
45 |
46 |
47 | [cite]: http://jsonapi.org/format/#url-based-json-api
48 |
--------------------------------------------------------------------------------
/rest_framework/static/rest_framework/js/default.js:
--------------------------------------------------------------------------------
1 | function getCookie(c_name)
2 | {
3 | // From http://www.w3schools.com/js/js_cookies.asp
4 | var c_value = document.cookie;
5 | var c_start = c_value.indexOf(" " + c_name + "=");
6 | if (c_start == -1) {
7 | c_start = c_value.indexOf(c_name + "=");
8 | }
9 | if (c_start == -1) {
10 | c_value = null;
11 | } else {
12 | c_start = c_value.indexOf("=", c_start) + 1;
13 | var c_end = c_value.indexOf(";", c_start);
14 | if (c_end == -1) {
15 | c_end = c_value.length;
16 | }
17 | c_value = unescape(c_value.substring(c_start,c_end));
18 | }
19 | return c_value;
20 | }
21 |
22 | // JSON highlighting.
23 | prettyPrint();
24 |
25 | // Bootstrap tooltips.
26 | $('.js-tooltip').tooltip({
27 | delay: 1000,
28 | container: 'body'
29 | });
30 |
31 | // Deal with rounded tab styling after tab clicks.
32 | $('a[data-toggle="tab"]:first').on('shown', function (e) {
33 | $(e.target).parents('.tabbable').addClass('first-tab-active');
34 | });
35 | $('a[data-toggle="tab"]:not(:first)').on('shown', function (e) {
36 | $(e.target).parents('.tabbable').removeClass('first-tab-active');
37 | });
38 |
39 | $('a[data-toggle="tab"]').click(function(){
40 | document.cookie="tabstyle=" + this.name + "; path=/";
41 | });
42 |
43 | // Store tab preference in cookies & display appropriate tab on load.
44 | var selectedTab = null;
45 | var selectedTabName = getCookie('tabstyle');
46 |
47 | if (selectedTabName) {
48 | selectedTabName = selectedTabName.replace(/[^a-z-]/g, '');
49 | }
50 |
51 | if (selectedTabName) {
52 | selectedTab = $('.form-switcher a[name=' + selectedTabName + ']');
53 | }
54 |
55 | if (selectedTab && selectedTab.length > 0) {
56 | // Display whichever tab is selected.
57 | selectedTab.tab('show');
58 | } else {
59 | // If no tab selected, display rightmost tab.
60 | $('.form-switcher a:first').tab('show');
61 | }
62 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/horizontal/radio.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "None" as none_choice %}
3 |
4 |
48 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/vertical/radio.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% trans "None" as none_choice %}
3 |
4 |
48 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | def pytest_configure():
2 | from django.conf import settings
3 |
4 | settings.configure(
5 | DEBUG_PROPAGATE_EXCEPTIONS=True,
6 | DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3',
7 | 'NAME': ':memory:'}},
8 | SITE_ID=1,
9 | SECRET_KEY='not very secret in tests',
10 | USE_I18N=True,
11 | USE_L10N=True,
12 | STATIC_URL='/static/',
13 | ROOT_URLCONF='tests.urls',
14 | TEMPLATE_LOADERS=(
15 | 'django.template.loaders.filesystem.Loader',
16 | 'django.template.loaders.app_directories.Loader',
17 | ),
18 | MIDDLEWARE_CLASSES=(
19 | 'django.middleware.common.CommonMiddleware',
20 | 'django.contrib.sessions.middleware.SessionMiddleware',
21 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
22 | 'django.contrib.messages.middleware.MessageMiddleware',
23 | ),
24 | INSTALLED_APPS=(
25 | 'django.contrib.auth',
26 | 'django.contrib.contenttypes',
27 | 'django.contrib.sessions',
28 | 'django.contrib.sites',
29 | 'django.contrib.staticfiles',
30 |
31 | 'rest_framework',
32 | 'rest_framework.authtoken',
33 | 'tests',
34 | ),
35 | PASSWORD_HASHERS=(
36 | 'django.contrib.auth.hashers.MD5PasswordHasher',
37 | ),
38 | )
39 |
40 | # guardian is optional
41 | try:
42 | import guardian # NOQA
43 | except ImportError:
44 | pass
45 | else:
46 | settings.ANONYMOUS_USER_ID = -1
47 | settings.AUTHENTICATION_BACKENDS = (
48 | 'django.contrib.auth.backends.ModelBackend',
49 | 'guardian.backends.ObjectPermissionBackend',
50 | )
51 | settings.INSTALLED_APPS += (
52 | 'guardian',
53 | )
54 |
55 | try:
56 | import django
57 | django.setup()
58 | except AttributeError:
59 | pass
60 |
--------------------------------------------------------------------------------
/rest_framework/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 | from django.utils.html import escape
6 | from django.utils.safestring import mark_safe
7 | from rest_framework.compat import apply_markdown, force_text
8 | import re
9 |
10 |
11 | def remove_trailing_string(content, trailing):
12 | """
13 | Strip trailing component `trailing` from `content` if it exists.
14 | Used when generating names from view classes.
15 | """
16 | if content.endswith(trailing) and content != trailing:
17 | return content[:-len(trailing)]
18 | return content
19 |
20 |
21 | def dedent(content):
22 | """
23 | Remove leading indent from a block of text.
24 | Used when generating descriptions from docstrings.
25 |
26 | Note that python's `textwrap.dedent` doesn't quite cut it,
27 | as it fails to dedent multiline docstrings that include
28 | unindented text on the initial line.
29 | """
30 | content = force_text(content)
31 | whitespace_counts = [len(line) - len(line.lstrip(' '))
32 | for line in content.splitlines()[1:] if line.lstrip()]
33 |
34 | # unindent the content if needed
35 | if whitespace_counts:
36 | whitespace_pattern = '^' + (' ' * min(whitespace_counts))
37 | content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
38 |
39 | return content.strip()
40 |
41 |
42 | def camelcase_to_spaces(content):
43 | """
44 | Translate 'CamelCaseNames' to 'Camel Case Names'.
45 | Used when generating names from view classes.
46 | """
47 | camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
48 | content = re.sub(camelcase_boundry, ' \\1', content).strip()
49 | return ' '.join(content.split('_')).title()
50 |
51 |
52 | def markup_description(description):
53 | """
54 | Apply HTML markup to the given description.
55 | """
56 | if apply_markdown:
57 | description = apply_markdown(description)
58 | else:
59 | description = escape(description).replace('\n', '
')
60 | description = '' + description + '
'
61 | return mark_safe(description)
62 |
--------------------------------------------------------------------------------
/rest_framework/utils/breadcrumbs.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.core.urlresolvers import resolve, get_script_prefix
3 |
4 |
5 | def get_breadcrumbs(url):
6 | """
7 | Given a url returns a list of breadcrumbs, which are each a
8 | tuple of (name, url).
9 | """
10 |
11 | from rest_framework.settings import api_settings
12 | from rest_framework.views import APIView
13 |
14 | view_name_func = api_settings.VIEW_NAME_FUNCTION
15 |
16 | def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen):
17 | """
18 | Add tuples of (name, url) to the breadcrumbs list,
19 | progressively chomping off parts of the url.
20 | """
21 |
22 | try:
23 | (view, unused_args, unused_kwargs) = resolve(url)
24 | except Exception:
25 | pass
26 | else:
27 | # Check if this is a REST framework view,
28 | # and if so add it to the breadcrumbs
29 | cls = getattr(view, 'cls', None)
30 | if cls is not None and issubclass(cls, APIView):
31 | # Don't list the same view twice in a row.
32 | # Probably an optional trailing slash.
33 | if not seen or seen[-1] != view:
34 | suffix = getattr(view, 'suffix', None)
35 | name = view_name_func(cls, suffix)
36 | breadcrumbs_list.insert(0, (name, prefix + 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 |
--------------------------------------------------------------------------------
/rest_framework/status.py:
--------------------------------------------------------------------------------
1 | """
2 | Descriptive HTTP status codes, for code readability.
3 |
4 | See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
5 | And RFC 6585 - http://tools.ietf.org/html/rfc6585
6 | """
7 | from __future__ import unicode_literals
8 |
9 |
10 | def is_informational(code):
11 | return code >= 100 and code <= 199
12 |
13 |
14 | def is_success(code):
15 | return code >= 200 and code <= 299
16 |
17 |
18 | def is_redirect(code):
19 | return code >= 300 and code <= 399
20 |
21 |
22 | def is_client_error(code):
23 | return code >= 400 and code <= 499
24 |
25 |
26 | def is_server_error(code):
27 | return code >= 500 and code <= 599
28 |
29 |
30 | HTTP_100_CONTINUE = 100
31 | HTTP_101_SWITCHING_PROTOCOLS = 101
32 | HTTP_200_OK = 200
33 | HTTP_201_CREATED = 201
34 | HTTP_202_ACCEPTED = 202
35 | HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
36 | HTTP_204_NO_CONTENT = 204
37 | HTTP_205_RESET_CONTENT = 205
38 | HTTP_206_PARTIAL_CONTENT = 206
39 | HTTP_300_MULTIPLE_CHOICES = 300
40 | HTTP_301_MOVED_PERMANENTLY = 301
41 | HTTP_302_FOUND = 302
42 | HTTP_303_SEE_OTHER = 303
43 | HTTP_304_NOT_MODIFIED = 304
44 | HTTP_305_USE_PROXY = 305
45 | HTTP_306_RESERVED = 306
46 | HTTP_307_TEMPORARY_REDIRECT = 307
47 | HTTP_400_BAD_REQUEST = 400
48 | HTTP_401_UNAUTHORIZED = 401
49 | HTTP_402_PAYMENT_REQUIRED = 402
50 | HTTP_403_FORBIDDEN = 403
51 | HTTP_404_NOT_FOUND = 404
52 | HTTP_405_METHOD_NOT_ALLOWED = 405
53 | HTTP_406_NOT_ACCEPTABLE = 406
54 | HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
55 | HTTP_408_REQUEST_TIMEOUT = 408
56 | HTTP_409_CONFLICT = 409
57 | HTTP_410_GONE = 410
58 | HTTP_411_LENGTH_REQUIRED = 411
59 | HTTP_412_PRECONDITION_FAILED = 412
60 | HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
61 | HTTP_414_REQUEST_URI_TOO_LONG = 414
62 | HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
63 | HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
64 | HTTP_417_EXPECTATION_FAILED = 417
65 | HTTP_428_PRECONDITION_REQUIRED = 428
66 | HTTP_429_TOO_MANY_REQUESTS = 429
67 | HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
68 | HTTP_500_INTERNAL_SERVER_ERROR = 500
69 | HTTP_501_NOT_IMPLEMENTED = 501
70 | HTTP_502_BAD_GATEWAY = 502
71 | HTTP_503_SERVICE_UNAVAILABLE = 503
72 | HTTP_504_GATEWAY_TIMEOUT = 504
73 | HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
74 | HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
75 |
--------------------------------------------------------------------------------
/tests/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.db import models
3 | from django.utils.translation import ugettext_lazy as _
4 |
5 |
6 | class RESTFrameworkModel(models.Model):
7 | """
8 | Base for test models that sets app_label, so they play nicely.
9 | """
10 |
11 | class Meta:
12 | app_label = 'tests'
13 | abstract = True
14 |
15 |
16 | class BasicModel(RESTFrameworkModel):
17 | text = models.CharField(max_length=100, verbose_name=_("Text comes here"), help_text=_("Text description."))
18 |
19 |
20 | class BaseFilterableItem(RESTFrameworkModel):
21 | text = models.CharField(max_length=100)
22 |
23 | class Meta:
24 | abstract = True
25 |
26 |
27 | class FilterableItem(BaseFilterableItem):
28 | decimal = models.DecimalField(max_digits=4, decimal_places=2)
29 | date = models.DateField()
30 |
31 |
32 | # Models for relations tests
33 | # ManyToMany
34 | class ManyToManyTarget(RESTFrameworkModel):
35 | name = models.CharField(max_length=100)
36 |
37 |
38 | class ManyToManySource(RESTFrameworkModel):
39 | name = models.CharField(max_length=100)
40 | targets = models.ManyToManyField(ManyToManyTarget, related_name='sources')
41 |
42 |
43 | # ForeignKey
44 | class ForeignKeyTarget(RESTFrameworkModel):
45 | name = models.CharField(max_length=100)
46 |
47 |
48 | class ForeignKeySource(RESTFrameworkModel):
49 | name = models.CharField(max_length=100)
50 | target = models.ForeignKey(ForeignKeyTarget, related_name='sources',
51 | help_text='Target', verbose_name='Target')
52 |
53 |
54 | # Nullable ForeignKey
55 | class NullableForeignKeySource(RESTFrameworkModel):
56 | name = models.CharField(max_length=100)
57 | target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True,
58 | related_name='nullable_sources',
59 | verbose_name='Optional target object')
60 |
61 |
62 | # OneToOne
63 | class OneToOneTarget(RESTFrameworkModel):
64 | name = models.CharField(max_length=100)
65 |
66 |
67 | class NullableOneToOneSource(RESTFrameworkModel):
68 | name = models.CharField(max_length=100)
69 | target = models.OneToOneField(OneToOneTarget, null=True, blank=True,
70 | related_name='nullable_source')
71 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ObjectDoesNotExist
2 | from django.core.urlresolvers import NoReverseMatch
3 |
4 |
5 | class UsingURLPatterns(object):
6 | """
7 | Isolates URL patterns used during testing on the test class itself.
8 | For example:
9 |
10 | class MyTestCase(UsingURLPatterns, TestCase):
11 | urlpatterns = [
12 | ...
13 | ]
14 |
15 | def test_something(self):
16 | ...
17 | """
18 | urls = __name__
19 |
20 | def setUp(self):
21 | global urlpatterns
22 | urlpatterns = self.urlpatterns
23 |
24 | def tearDown(self):
25 | global urlpatterns
26 | urlpatterns = []
27 |
28 |
29 | class MockObject(object):
30 | def __init__(self, **kwargs):
31 | self._kwargs = kwargs
32 | for key, val in kwargs.items():
33 | setattr(self, key, val)
34 |
35 | def __str__(self):
36 | kwargs_str = ', '.join([
37 | '%s=%s' % (key, value)
38 | for key, value in sorted(self._kwargs.items())
39 | ])
40 | return '' % kwargs_str
41 |
42 |
43 | class MockQueryset(object):
44 | def __init__(self, iterable):
45 | self.items = iterable
46 |
47 | def get(self, **lookup):
48 | for item in self.items:
49 | if all([
50 | getattr(item, key, None) == value
51 | for key, value in lookup.items()
52 | ]):
53 | return item
54 | raise ObjectDoesNotExist()
55 |
56 |
57 | class BadType(object):
58 | """
59 | When used as a lookup with a `MockQueryset`, these objects
60 | will raise a `TypeError`, as occurs in Django when making
61 | queryset lookups with an incorrect type for the lookup value.
62 | """
63 | def __eq__(self):
64 | raise TypeError()
65 |
66 |
67 | def mock_reverse(view_name, args=None, kwargs=None, request=None, format=None):
68 | args = args or []
69 | kwargs = kwargs or {}
70 | value = (args + list(kwargs.values()) + ['-'])[0]
71 | prefix = 'http://example.org' if request else ''
72 | suffix = ('.' + format) if (format is not None) else ''
73 | return '%s/%s/%s%s/' % (prefix, view_name, value, suffix)
74 |
75 |
76 | def fail_reverse(view_name, args=None, kwargs=None, request=None, format=None):
77 | raise NoReverseMatch()
78 |
--------------------------------------------------------------------------------
/tests/test_multitable_inheritance.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.db import models
3 | from django.test import TestCase
4 | from rest_framework import serializers
5 | from tests.models import RESTFrameworkModel
6 |
7 |
8 | # Models
9 | class ParentModel(RESTFrameworkModel):
10 | name1 = models.CharField(max_length=100)
11 |
12 |
13 | class ChildModel(ParentModel):
14 | name2 = models.CharField(max_length=100)
15 |
16 |
17 | class AssociatedModel(RESTFrameworkModel):
18 | ref = models.OneToOneField(ParentModel, primary_key=True)
19 | name = models.CharField(max_length=100)
20 |
21 |
22 | # Serializers
23 | class DerivedModelSerializer(serializers.ModelSerializer):
24 | class Meta:
25 | model = ChildModel
26 |
27 |
28 | class AssociatedModelSerializer(serializers.ModelSerializer):
29 | class Meta:
30 | model = AssociatedModel
31 |
32 |
33 | # Tests
34 | class InheritedModelSerializationTests(TestCase):
35 |
36 | def test_multitable_inherited_model_fields_as_expected(self):
37 | """
38 | Assert that the parent pointer field is not included in the fields
39 | serialized fields
40 | """
41 | child = ChildModel(name1='parent name', name2='child name')
42 | serializer = DerivedModelSerializer(child)
43 | self.assertEqual(set(serializer.data.keys()),
44 | set(['name1', 'name2', 'id']))
45 |
46 | def test_onetoone_primary_key_model_fields_as_expected(self):
47 | """
48 | Assert that a model with a onetoone field that is the primary key is
49 | not treated like a derived model
50 | """
51 | parent = ParentModel.objects.create(name1='parent name')
52 | associate = AssociatedModel.objects.create(name='hello', ref=parent)
53 | serializer = AssociatedModelSerializer(associate)
54 | self.assertEqual(set(serializer.data.keys()),
55 | set(['name', 'ref']))
56 |
57 | def test_data_is_valid_without_parent_ptr(self):
58 | """
59 | Assert that the pointer to the parent table is not a required field
60 | for input data
61 | """
62 | data = {
63 | 'name1': 'parent name',
64 | 'name2': 'child name',
65 | }
66 | serializer = DerivedModelSerializer(data=data)
67 | self.assertEqual(serializer.is_valid(), True)
68 |
--------------------------------------------------------------------------------
/rest_framework/utils/html.py:
--------------------------------------------------------------------------------
1 | """
2 | Helpers for dealing with HTML input.
3 | """
4 | import re
5 | from django.utils.datastructures import MultiValueDict
6 |
7 |
8 | def is_html_input(dictionary):
9 | # MultiDict type datastructures are used to represent HTML form input,
10 | # which may have more than one value for each key.
11 | return hasattr(dictionary, 'getlist')
12 |
13 |
14 | def parse_html_list(dictionary, prefix=''):
15 | """
16 | Used to suport list values in HTML forms.
17 | Supports lists of primitives and/or dictionaries.
18 |
19 | * List of primitives.
20 |
21 | {
22 | '[0]': 'abc',
23 | '[1]': 'def',
24 | '[2]': 'hij'
25 | }
26 | -->
27 | [
28 | 'abc',
29 | 'def',
30 | 'hij'
31 | ]
32 |
33 | * List of dictionaries.
34 |
35 | {
36 | '[0]foo': 'abc',
37 | '[0]bar': 'def',
38 | '[1]foo': 'hij',
39 | '[1]bar': 'klm',
40 | }
41 | -->
42 | [
43 | {'foo': 'abc', 'bar': 'def'},
44 | {'foo': 'hij', 'bar': 'klm'}
45 | ]
46 | """
47 | ret = {}
48 | regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix))
49 | for field, value in dictionary.items():
50 | match = regex.match(field)
51 | if not match:
52 | continue
53 | index, key = match.groups()
54 | index = int(index)
55 | if not key:
56 | ret[index] = value
57 | elif isinstance(ret.get(index), dict):
58 | ret[index][key] = value
59 | else:
60 | ret[index] = MultiValueDict({key: [value]})
61 | return [ret[item] for item in sorted(ret.keys())]
62 |
63 |
64 | def parse_html_dict(dictionary, prefix):
65 | """
66 | Used to support dictionary values in HTML forms.
67 |
68 | {
69 | 'profile.username': 'example',
70 | 'profile.email': 'example@example.com',
71 | }
72 | -->
73 | {
74 | 'profile': {
75 | 'username': 'example',
76 | 'email': 'example@example.com'
77 | }
78 | }
79 | """
80 | ret = {}
81 | regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
82 | for field, value in dictionary.items():
83 | match = regex.match(field)
84 | if not match:
85 | continue
86 | key = match.groups()[0]
87 | ret[key] = value
88 | return ret
89 |
--------------------------------------------------------------------------------
/rest_framework/utils/encoders.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper classes for parsers.
3 | """
4 | from __future__ import unicode_literals
5 | from django.db.models.query import QuerySet
6 | from django.utils import six, timezone
7 | from django.utils.encoding import force_text
8 | from django.utils.functional import Promise
9 | from rest_framework.compat import total_seconds
10 | import datetime
11 | import decimal
12 | import json
13 | import uuid
14 |
15 |
16 | class JSONEncoder(json.JSONEncoder):
17 | """
18 | JSONEncoder subclass that knows how to encode date/time/timedelta,
19 | decimal types, generators and other basic python objects.
20 | """
21 | def default(self, obj):
22 | # For Date Time string spec, see ECMA 262
23 | # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
24 | if isinstance(obj, Promise):
25 | return force_text(obj)
26 | elif isinstance(obj, datetime.datetime):
27 | representation = obj.isoformat()
28 | if obj.microsecond:
29 | representation = representation[:23] + representation[26:]
30 | if representation.endswith('+00:00'):
31 | representation = representation[:-6] + 'Z'
32 | return representation
33 | elif isinstance(obj, datetime.date):
34 | return obj.isoformat()
35 | elif isinstance(obj, datetime.time):
36 | if timezone and timezone.is_aware(obj):
37 | raise ValueError("JSON can't represent timezone-aware times.")
38 | representation = obj.isoformat()
39 | if obj.microsecond:
40 | representation = representation[:12]
41 | return representation
42 | elif isinstance(obj, datetime.timedelta):
43 | return six.text_type(total_seconds(obj))
44 | elif isinstance(obj, decimal.Decimal):
45 | # Serializers will coerce decimals to strings by default.
46 | return float(obj)
47 | elif isinstance(obj, uuid.UUID):
48 | return six.text_type(obj)
49 | elif isinstance(obj, QuerySet):
50 | return tuple(obj)
51 | elif hasattr(obj, 'tolist'):
52 | # Numpy arrays and array scalars.
53 | return obj.tolist()
54 | elif hasattr(obj, '__getitem__'):
55 | try:
56 | return dict(obj)
57 | except:
58 | pass
59 | elif hasattr(obj, '__iter__'):
60 | return tuple(item for item in obj)
61 | return super(JSONEncoder, self).default(obj)
62 |
--------------------------------------------------------------------------------
/tests/browsable_api/test_browsable_api.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.contrib.auth.models import User
3 | from django.test import TestCase
4 |
5 | from rest_framework.test import APIClient
6 |
7 |
8 | class DropdownWithAuthTests(TestCase):
9 | """Tests correct dropdown behaviour with Auth views enabled."""
10 |
11 | urls = 'tests.browsable_api.auth_urls'
12 |
13 | def setUp(self):
14 | self.client = APIClient(enforce_csrf_checks=True)
15 | self.username = 'john'
16 | self.email = 'lennon@thebeatles.com'
17 | self.password = 'password'
18 | self.user = User.objects.create_user(self.username, self.email, self.password)
19 |
20 | def tearDown(self):
21 | self.client.logout()
22 |
23 | def test_name_shown_when_logged_in(self):
24 | self.client.login(username=self.username, password=self.password)
25 | response = self.client.get('/')
26 | self.assertContains(response, 'john')
27 |
28 | def test_logout_shown_when_logged_in(self):
29 | self.client.login(username=self.username, password=self.password)
30 | response = self.client.get('/')
31 | self.assertContains(response, '>Log out<')
32 |
33 | def test_login_shown_when_logged_out(self):
34 | response = self.client.get('/')
35 | self.assertContains(response, '>Log in<')
36 |
37 |
38 | class NoDropdownWithoutAuthTests(TestCase):
39 | """Tests correct dropdown behaviour with Auth views NOT enabled."""
40 |
41 | urls = 'tests.browsable_api.no_auth_urls'
42 |
43 | def setUp(self):
44 | self.client = APIClient(enforce_csrf_checks=True)
45 | self.username = 'john'
46 | self.email = 'lennon@thebeatles.com'
47 | self.password = 'password'
48 | self.user = User.objects.create_user(self.username, self.email, self.password)
49 |
50 | def tearDown(self):
51 | self.client.logout()
52 |
53 | def test_name_shown_when_logged_in(self):
54 | self.client.login(username=self.username, password=self.password)
55 | response = self.client.get('/')
56 | self.assertContains(response, 'john')
57 |
58 | def test_dropdown_not_shown_when_logged_in(self):
59 | self.client.login(username=self.username, password=self.password)
60 | response = self.client.get('/')
61 | self.assertNotContains(response, '')
62 |
63 | def test_dropdown_not_shown_when_logged_out(self):
64 | response = self.client.get('/')
65 | self.assertNotContains(response, '')
66 |
--------------------------------------------------------------------------------
/docs_theme/nav.html:
--------------------------------------------------------------------------------
1 |
48 |
--------------------------------------------------------------------------------
/rest_framework/urlpatterns.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.conf.urls import url, include
3 | from django.core.urlresolvers import RegexURLResolver
4 | from rest_framework.settings import api_settings
5 |
6 |
7 | def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required):
8 | ret = []
9 | for urlpattern in urlpatterns:
10 | if isinstance(urlpattern, RegexURLResolver):
11 | # Set of included URL patterns
12 | regex = urlpattern.regex.pattern
13 | namespace = urlpattern.namespace
14 | app_name = urlpattern.app_name
15 | kwargs = urlpattern.default_kwargs
16 | # Add in the included patterns, after applying the suffixes
17 | patterns = apply_suffix_patterns(urlpattern.url_patterns,
18 | suffix_pattern,
19 | suffix_required)
20 | ret.append(url(regex, include(patterns, namespace, app_name), kwargs))
21 |
22 | else:
23 | # Regular URL pattern
24 | regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern
25 | view = urlpattern._callback or urlpattern._callback_str
26 | kwargs = urlpattern.default_args
27 | name = urlpattern.name
28 | # Add in both the existing and the new urlpattern
29 | if not suffix_required:
30 | ret.append(urlpattern)
31 | ret.append(url(regex, view, kwargs, name))
32 |
33 | return ret
34 |
35 |
36 | def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
37 | """
38 | Supplement existing urlpatterns with corresponding patterns that also
39 | include a '.format' suffix. Retains urlpattern ordering.
40 |
41 | urlpatterns:
42 | A list of URL patterns.
43 |
44 | suffix_required:
45 | If `True`, only suffixed URLs will be generated, and non-suffixed
46 | URLs will not be used. Defaults to `False`.
47 |
48 | allowed:
49 | An optional tuple/list of allowed suffixes. eg ['json', 'api']
50 | Defaults to `None`, which allows any suffix.
51 | """
52 | suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG
53 | if allowed:
54 | if len(allowed) == 1:
55 | allowed_pattern = allowed[0]
56 | else:
57 | allowed_pattern = '(%s)' % '|'.join(allowed)
58 | suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern)
59 | else:
60 | suffix_pattern = r'\.(?P<%s>[a-z0-9]+)$' % suffix_kwarg
61 |
62 | return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required)
63 |
--------------------------------------------------------------------------------
/tests/test_templatetags.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 | from django.test import TestCase
4 | from rest_framework.test import APIRequestFactory
5 | from rest_framework.templatetags.rest_framework import add_query_param, urlize_quoted_links
6 |
7 |
8 | factory = APIRequestFactory()
9 |
10 |
11 | class TemplateTagTests(TestCase):
12 |
13 | def test_add_query_param_with_non_latin_charactor(self):
14 | # Ensure we don't double-escape non-latin characters
15 | # that are present in the querystring.
16 | # See #1314.
17 | request = factory.get("/", {'q': '查询'})
18 | json_url = add_query_param(request, "format", "json")
19 | self.assertIn("q=%E6%9F%A5%E8%AF%A2", json_url)
20 | self.assertIn("format=json", json_url)
21 |
22 |
23 | class Issue1386Tests(TestCase):
24 | """
25 | Covers #1386
26 | """
27 |
28 | def test_issue_1386(self):
29 | """
30 | Test function urlize_quoted_links with different args
31 | """
32 | correct_urls = [
33 | "asdf.com",
34 | "asdf.net",
35 | "www.as_df.org",
36 | "as.d8f.ghj8.gov",
37 | ]
38 | for i in correct_urls:
39 | res = urlize_quoted_links(i)
40 | self.assertNotEqual(res, i)
41 | self.assertIn(i, res)
42 |
43 | incorrect_urls = [
44 | "mailto://asdf@fdf.com",
45 | "asdf.netnet",
46 | ]
47 | for i in incorrect_urls:
48 | res = urlize_quoted_links(i)
49 | self.assertEqual(i, res)
50 |
51 | # example from issue #1386, this shouldn't raise an exception
52 | urlize_quoted_links("asdf:[/p]zxcv.com")
53 |
54 |
55 | class URLizerTests(TestCase):
56 | """
57 | Test if JSON URLs are transformed into links well
58 | """
59 | def _urlize_dict_check(self, data):
60 | """
61 | For all items in dict test assert that the value is urlized key
62 | """
63 | for original, urlized in data.items():
64 | assert urlize_quoted_links(original, nofollow=False) == urlized
65 |
66 | def test_json_with_url(self):
67 | """
68 | Test if JSON URLs are transformed into links well
69 | """
70 | data = {}
71 | data['"url": "http://api/users/1/", '] = \
72 | '"url": "http://api/users/1/", '
73 | data['"foo_set": [\n "http://api/foos/1/"\n], '] = \
74 | '"foo_set": [\n "http://api/foos/1/"\n], '
75 | self._urlize_dict_check(data)
76 |
--------------------------------------------------------------------------------
/docs/api-guide/reverse.md:
--------------------------------------------------------------------------------
1 | source: reverse.py
2 |
3 | # Returning URLs
4 |
5 | > The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components.
6 | >
7 | > — Roy Fielding, [Architectural Styles and the Design of Network-based Software Architectures][cite]
8 |
9 | As a rule, it's probably better practice to return absolute URIs from your Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`.
10 |
11 | The advantages of doing so are:
12 |
13 | * It's more explicit.
14 | * It leaves less work for your API clients.
15 | * There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type.
16 | * It makes it easy to do things like markup HTML representations with hyperlinks.
17 |
18 | REST framework provides two utility functions to make it more simple to return absolute URIs from your Web API.
19 |
20 | There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink its output for you, which makes browsing the API much easier.
21 |
22 | ## reverse
23 |
24 | **Signature:** `reverse(viewname, *args, **kwargs)`
25 |
26 | Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
27 |
28 | You should **include the request as a keyword argument** to the function, for example:
29 |
30 | from rest_framework.reverse import reverse
31 | from rest_framework.views import APIView
32 | from django.utils.timezone import now
33 |
34 | class APIRootView(APIView):
35 | def get(self, request):
36 | year = now().year
37 | data = {
38 | ...
39 | 'year-summary-url': reverse('year-summary', args=[year], request=request)
40 | }
41 | return Response(data)
42 |
43 | ## reverse_lazy
44 |
45 | **Signature:** `reverse_lazy(viewname, *args, **kwargs)`
46 |
47 | Has the same behavior as [`django.core.urlresolvers.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
48 |
49 | As with the `reverse` function, you should **include the request as a keyword argument** to the function, for example:
50 |
51 | api_root = reverse_lazy('api-root', request=request)
52 |
53 | [cite]: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
54 | [reverse]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
55 | [reverse-lazy]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy
56 |
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 | from __future__ import print_function
3 |
4 | import pytest
5 | import sys
6 | import os
7 | import subprocess
8 |
9 |
10 | PYTEST_ARGS = {
11 | 'default': ['tests', '--tb=short'],
12 | 'fast': ['tests', '--tb=short', '-q'],
13 | }
14 |
15 | FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501']
16 |
17 |
18 | sys.path.append(os.path.dirname(__file__))
19 |
20 |
21 | def exit_on_failure(ret, message=None):
22 | if ret:
23 | sys.exit(ret)
24 |
25 |
26 | def flake8_main(args):
27 | print('Running flake8 code linting')
28 | ret = subprocess.call(['flake8'] + args)
29 | print('flake8 failed' if ret else 'flake8 passed')
30 | return ret
31 |
32 |
33 | def split_class_and_function(string):
34 | class_string, function_string = string.split('.', 1)
35 | return "%s and %s" % (class_string, function_string)
36 |
37 |
38 | def is_function(string):
39 | # `True` if it looks like a test function is included in the string.
40 | return string.startswith('test_') or '.test_' in string
41 |
42 |
43 | def is_class(string):
44 | # `True` if first character is uppercase - assume it's a class name.
45 | return string[0] == string[0].upper()
46 |
47 |
48 | if __name__ == "__main__":
49 | try:
50 | sys.argv.remove('--nolint')
51 | except ValueError:
52 | run_flake8 = True
53 | else:
54 | run_flake8 = False
55 |
56 | try:
57 | sys.argv.remove('--lintonly')
58 | except ValueError:
59 | run_tests = True
60 | else:
61 | run_tests = False
62 |
63 | try:
64 | sys.argv.remove('--fast')
65 | except ValueError:
66 | style = 'default'
67 | else:
68 | style = 'fast'
69 | run_flake8 = False
70 |
71 | if len(sys.argv) > 1:
72 | pytest_args = sys.argv[1:]
73 | first_arg = pytest_args[0]
74 | if first_arg.startswith('-'):
75 | # `runtests.py [flags]`
76 | pytest_args = ['tests'] + pytest_args
77 | elif is_class(first_arg) and is_function(first_arg):
78 | # `runtests.py TestCase.test_function [flags]`
79 | expression = split_class_and_function(first_arg)
80 | pytest_args = ['tests', '-k', expression] + pytest_args[1:]
81 | elif is_class(first_arg) or is_function(first_arg):
82 | # `runtests.py TestCase [flags]`
83 | # `runtests.py test_function [flags]`
84 | pytest_args = ['tests', '-k', pytest_args[0]] + pytest_args[1:]
85 | else:
86 | pytest_args = PYTEST_ARGS[style]
87 |
88 | if run_tests:
89 | exit_on_failure(pytest.main(pytest_args))
90 | if run_flake8:
91 | exit_on_failure(flake8_main(FLAKE8_ARGS))
92 |
--------------------------------------------------------------------------------
/rest_framework/utils/mediatypes.py:
--------------------------------------------------------------------------------
1 | """
2 | Handling of media types, as found in HTTP Content-Type and Accept headers.
3 |
4 | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
5 | """
6 | from __future__ import unicode_literals
7 | from django.http.multipartparser import parse_header
8 | from django.utils.encoding import python_2_unicode_compatible
9 | from rest_framework import HTTP_HEADER_ENCODING
10 |
11 |
12 | def media_type_matches(lhs, rhs):
13 | """
14 | Returns ``True`` if the media type in the first argument <= the
15 | media type in the second argument. The media types are strings
16 | as described by the HTTP spec.
17 |
18 | Valid media type strings include:
19 |
20 | 'application/json; indent=4'
21 | 'application/json'
22 | 'text/*'
23 | '*/*'
24 | """
25 | lhs = _MediaType(lhs)
26 | rhs = _MediaType(rhs)
27 | return lhs.match(rhs)
28 |
29 |
30 | def order_by_precedence(media_type_lst):
31 | """
32 | Returns a list of sets of media type strings, ordered by precedence.
33 | Precedence is determined by how specific a media type is:
34 |
35 | 3. 'type/subtype; param=val'
36 | 2. 'type/subtype'
37 | 1. 'type/*'
38 | 0. '*/*'
39 | """
40 | ret = [set(), set(), set(), set()]
41 | for media_type in media_type_lst:
42 | precedence = _MediaType(media_type).precedence
43 | ret[3 - precedence].add(media_type)
44 | return [media_types for media_types in ret if media_types]
45 |
46 |
47 | @python_2_unicode_compatible
48 | class _MediaType(object):
49 | def __init__(self, media_type_str):
50 | if media_type_str is None:
51 | media_type_str = ''
52 | self.orig = media_type_str
53 | self.full_type, self.params = parse_header(media_type_str.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.keys():
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.keys()) == ['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)
87 | return ret
88 |
--------------------------------------------------------------------------------
/rest_framework/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 | from rest_framework import status
9 | from rest_framework.response import Response
10 | from rest_framework.settings import api_settings
11 |
12 |
13 | class CreateModelMixin(object):
14 | """
15 | Create a model instance.
16 | """
17 | def create(self, request, *args, **kwargs):
18 | serializer = self.get_serializer(data=request.data)
19 | serializer.is_valid(raise_exception=True)
20 | self.perform_create(serializer)
21 | headers = self.get_success_headers(serializer.data)
22 | return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
23 |
24 | def perform_create(self, serializer):
25 | serializer.save()
26 |
27 | def get_success_headers(self, data):
28 | try:
29 | return {'Location': data[api_settings.URL_FIELD_NAME]}
30 | except (TypeError, KeyError):
31 | return {}
32 |
33 |
34 | class ListModelMixin(object):
35 | """
36 | List a queryset.
37 | """
38 | def list(self, request, *args, **kwargs):
39 | queryset = self.filter_queryset(self.get_queryset())
40 |
41 | page = self.paginate_queryset(queryset)
42 | if page is not None:
43 | serializer = self.get_serializer(page, many=True)
44 | return self.get_paginated_response(serializer.data)
45 |
46 | serializer = self.get_serializer(queryset, many=True)
47 | return Response(serializer.data)
48 |
49 |
50 | class RetrieveModelMixin(object):
51 | """
52 | Retrieve a model instance.
53 | """
54 | def retrieve(self, request, *args, **kwargs):
55 | instance = self.get_object()
56 | serializer = self.get_serializer(instance)
57 | return Response(serializer.data)
58 |
59 |
60 | class UpdateModelMixin(object):
61 | """
62 | Update a model instance.
63 | """
64 | def update(self, request, *args, **kwargs):
65 | partial = kwargs.pop('partial', False)
66 | instance = self.get_object()
67 | serializer = self.get_serializer(instance, data=request.data, partial=partial)
68 | serializer.is_valid(raise_exception=True)
69 | self.perform_update(serializer)
70 | return Response(serializer.data)
71 |
72 | def perform_update(self, serializer):
73 | serializer.save()
74 |
75 | def partial_update(self, request, *args, **kwargs):
76 | kwargs['partial'] = True
77 | return self.update(request, *args, **kwargs)
78 |
79 |
80 | class DestroyModelMixin(object):
81 | """
82 | Destroy a model instance.
83 | """
84 | def destroy(self, request, *args, **kwargs):
85 | instance = self.get_object()
86 | self.perform_destroy(instance)
87 | return Response(status=status.HTTP_204_NO_CONTENT)
88 |
89 | def perform_destroy(self, instance):
90 | instance.delete()
91 |
--------------------------------------------------------------------------------
/tests/test_bound_fields.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 |
4 | class TestSimpleBoundField:
5 | def test_empty_bound_field(self):
6 | class ExampleSerializer(serializers.Serializer):
7 | text = serializers.CharField(max_length=100)
8 | amount = serializers.IntegerField()
9 |
10 | serializer = ExampleSerializer()
11 |
12 | assert serializer['text'].value == ''
13 | assert serializer['text'].errors is None
14 | assert serializer['text'].name == 'text'
15 | assert serializer['amount'].value is None
16 | assert serializer['amount'].errors is None
17 | assert serializer['amount'].name == 'amount'
18 |
19 | def test_populated_bound_field(self):
20 | class ExampleSerializer(serializers.Serializer):
21 | text = serializers.CharField(max_length=100)
22 | amount = serializers.IntegerField()
23 |
24 | serializer = ExampleSerializer(data={'text': 'abc', 'amount': 123})
25 | assert serializer.is_valid()
26 | assert serializer['text'].value == 'abc'
27 | assert serializer['text'].errors is None
28 | assert serializer['text'].name == 'text'
29 | assert serializer['amount'].value is 123
30 | assert serializer['amount'].errors is None
31 | assert serializer['amount'].name == 'amount'
32 |
33 | def test_error_bound_field(self):
34 | class ExampleSerializer(serializers.Serializer):
35 | text = serializers.CharField(max_length=100)
36 | amount = serializers.IntegerField()
37 |
38 | serializer = ExampleSerializer(data={'text': 'x' * 1000, 'amount': 123})
39 | serializer.is_valid()
40 |
41 | assert serializer['text'].value == 'x' * 1000
42 | assert serializer['text'].errors == ['Ensure this field has no more than 100 characters.']
43 | assert serializer['text'].name == 'text'
44 | assert serializer['amount'].value is 123
45 | assert serializer['amount'].errors is None
46 | assert serializer['amount'].name == 'amount'
47 |
48 |
49 | class TestNestedBoundField:
50 | def test_nested_empty_bound_field(self):
51 | class Nested(serializers.Serializer):
52 | more_text = serializers.CharField(max_length=100)
53 | amount = serializers.IntegerField()
54 |
55 | class ExampleSerializer(serializers.Serializer):
56 | text = serializers.CharField(max_length=100)
57 | nested = Nested()
58 |
59 | serializer = ExampleSerializer()
60 |
61 | assert serializer['text'].value == ''
62 | assert serializer['text'].errors is None
63 | assert serializer['text'].name == 'text'
64 | assert serializer['nested']['more_text'].value == ''
65 | assert serializer['nested']['more_text'].errors is None
66 | assert serializer['nested']['more_text'].name == 'nested.more_text'
67 | assert serializer['nested']['amount'].value is None
68 | assert serializer['nested']['amount'].errors is None
69 | assert serializer['nested']['amount'].name == 'nested.amount'
70 |
--------------------------------------------------------------------------------
/tests/test_urlpatterns.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from collections import namedtuple
3 | from django.conf.urls import patterns, url, include
4 | from django.core import urlresolvers
5 | from django.test import TestCase
6 | from rest_framework.test import APIRequestFactory
7 | from rest_framework.urlpatterns import format_suffix_patterns
8 |
9 |
10 | # A container class for test paths for the test case
11 | URLTestPath = namedtuple('URLTestPath', ['path', 'args', 'kwargs'])
12 |
13 |
14 | def dummy_view(request, *args, **kwargs):
15 | pass
16 |
17 |
18 | class FormatSuffixTests(TestCase):
19 | """
20 | Tests `format_suffix_patterns` against different URLPatterns to ensure the URLs still resolve properly, including any captured parameters.
21 | """
22 | def _resolve_urlpatterns(self, urlpatterns, test_paths):
23 | factory = APIRequestFactory()
24 | try:
25 | urlpatterns = format_suffix_patterns(urlpatterns)
26 | except Exception:
27 | self.fail("Failed to apply `format_suffix_patterns` on the supplied urlpatterns")
28 | resolver = urlresolvers.RegexURLResolver(r'^/', urlpatterns)
29 | for test_path in test_paths:
30 | request = factory.get(test_path.path)
31 | try:
32 | callback, callback_args, callback_kwargs = resolver.resolve(request.path_info)
33 | except Exception:
34 | self.fail("Failed to resolve URL: %s" % request.path_info)
35 | self.assertEqual(callback_args, test_path.args)
36 | self.assertEqual(callback_kwargs, test_path.kwargs)
37 |
38 | def test_format_suffix(self):
39 | urlpatterns = patterns(
40 | '',
41 | url(r'^test$', dummy_view),
42 | )
43 | test_paths = [
44 | URLTestPath('/test', (), {}),
45 | URLTestPath('/test.api', (), {'format': 'api'}),
46 | URLTestPath('/test.asdf', (), {'format': 'asdf'}),
47 | ]
48 | self._resolve_urlpatterns(urlpatterns, test_paths)
49 |
50 | def test_default_args(self):
51 | urlpatterns = patterns(
52 | '',
53 | url(r'^test$', dummy_view, {'foo': 'bar'}),
54 | )
55 | test_paths = [
56 | URLTestPath('/test', (), {'foo': 'bar', }),
57 | URLTestPath('/test.api', (), {'foo': 'bar', 'format': 'api'}),
58 | URLTestPath('/test.asdf', (), {'foo': 'bar', 'format': 'asdf'}),
59 | ]
60 | self._resolve_urlpatterns(urlpatterns, test_paths)
61 |
62 | def test_included_urls(self):
63 | nested_patterns = patterns(
64 | '',
65 | url(r'^path$', dummy_view)
66 | )
67 | urlpatterns = patterns(
68 | '',
69 | url(r'^test/', include(nested_patterns), {'foo': 'bar'}),
70 | )
71 | test_paths = [
72 | URLTestPath('/test/path', (), {'foo': 'bar', }),
73 | URLTestPath('/test/path.api', (), {'foo': 'bar', 'format': 'api'}),
74 | URLTestPath('/test/path.asdf', (), {'foo': 'bar', 'format': 'asdf'}),
75 | ]
76 | self._resolve_urlpatterns(urlpatterns, test_paths)
77 |
--------------------------------------------------------------------------------
/docs/topics/ajax-csrf-cors.md:
--------------------------------------------------------------------------------
1 | # Working with AJAX, CSRF & CORS
2 |
3 | > "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability — very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one."
4 | >
5 | > — [Jeff Atwood][cite]
6 |
7 | ## Javascript clients
8 |
9 | If you’re building a JavaScript client to interface with your Web API, you'll need to consider if the client can use the same authentication policy that is used by the rest of the website, and also determine if you need to use CSRF tokens or CORS headers.
10 |
11 | AJAX requests that are made within the same context as the API they are interacting with will typically use `SessionAuthentication`. This ensures that once a user has logged in, any AJAX requests made can be authenticated using the same session-based authentication that is used for the rest of the website.
12 |
13 | AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as `TokenAuthentication`.
14 |
15 | ## CSRF protection
16 |
17 | [Cross Site Request Forgery][csrf] protection is a mechanism of guarding against a particular type of attack, which can occur when a user has not logged out of a web site, and continues to have a valid session. In this circumstance a malicious site may be able to perform actions against the target site, within the context of the logged-in session.
18 |
19 | To guard against these type of attacks, you need to do two things:
20 |
21 | 1. Ensure that the 'safe' HTTP operations, such as `GET`, `HEAD` and `OPTIONS` cannot be used to alter any server-side state.
22 | 2. Ensure that any 'unsafe' HTTP operations, such as `POST`, `PUT`, `PATCH` and `DELETE`, always require a valid CSRF token.
23 |
24 | If you're using `SessionAuthentication` you'll need to include valid CSRF tokens for any `POST`, `PUT`, `PATCH` or `DELETE` operations.
25 |
26 | In order to make AJAX requests, you need to include CSRF token in the HTTP header, as [described in the Django documentation][csrf-ajax].
27 |
28 | ## CORS
29 |
30 | [Cross-Origin Resource Sharing][cors] is a mechanism for allowing clients to interact with APIs that are hosted on a different domain. CORS works by requiring the server to include a specific set of headers that allow a browser to determine if and when cross-domain requests should be allowed.
31 |
32 | The best way to deal with CORS in REST framework is to add the required response headers in middleware. This ensures that CORS is supported transparently, without having to change any behavior in your views.
33 |
34 | [Otto Yiu][ottoyiu] maintains the [django-cors-headers] package, which is known to work correctly with REST framework APIs.
35 |
36 | [cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
37 | [csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
38 | [csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
39 | [cors]: http://www.w3.org/TR/cors/
40 | [ottoyiu]: https://github.com/ottoyiu/
41 | [django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/
42 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup
5 | from setuptools.command.test import test as TestCommand
6 | import re
7 | import os
8 | import sys
9 |
10 |
11 | def get_version(package):
12 | """
13 | Return package version as listed in `__version__` in `init.py`.
14 | """
15 | init_py = open(os.path.join(package, '__init__.py')).read()
16 | return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1)
17 |
18 |
19 | def get_packages(package):
20 | """
21 | Return root package and all sub-packages.
22 | """
23 | return [dirpath
24 | for dirpath, dirnames, filenames in os.walk(package)
25 | if os.path.exists(os.path.join(dirpath, '__init__.py'))]
26 |
27 |
28 | def get_package_data(package):
29 | """
30 | Return all files under the root package, that are not in a
31 | package themselves.
32 | """
33 | walk = [(dirpath.replace(package + os.sep, '', 1), filenames)
34 | for dirpath, dirnames, filenames in os.walk(package)
35 | if not os.path.exists(os.path.join(dirpath, '__init__.py'))]
36 |
37 | filepaths = []
38 | for base, filenames in walk:
39 | filepaths.extend([os.path.join(base, filename)
40 | for filename in filenames])
41 | return {package: filepaths}
42 |
43 |
44 | version = get_version('rest_framework')
45 |
46 |
47 | if sys.argv[-1] == 'publish':
48 | if os.system("pip freeze | grep wheel"):
49 | print("wheel not installed.\nUse `pip install wheel`.\nExiting.")
50 | sys.exit()
51 | if os.system("pip freeze | grep twine"):
52 | print("twine not installed.\nUse `pip install twine`.\nExiting.")
53 | sys.exit()
54 | os.system("python setup.py sdist bdist_wheel")
55 | os.system("twine upload dist/*")
56 | print("You probably want to also tag the version now:")
57 | print(" git tag -a %s -m 'version %s'" % (version, version))
58 | print(" git push --tags")
59 | sys.exit()
60 |
61 |
62 | setup(
63 | name='djangorestframework',
64 | version=version,
65 | url='http://www.django-rest-framework.org',
66 | license='BSD',
67 | description='Web APIs for Django, made easy.',
68 | author='Tom Christie',
69 | author_email='tom@tomchristie.com', # SEE NOTE BELOW (*)
70 | packages=get_packages('rest_framework'),
71 | package_data=get_package_data('rest_framework'),
72 | install_requires=[],
73 | zip_safe=False,
74 | classifiers=[
75 | 'Development Status :: 5 - Production/Stable',
76 | 'Environment :: Web Environment',
77 | 'Framework :: Django',
78 | 'Intended Audience :: Developers',
79 | 'License :: OSI Approved :: BSD License',
80 | 'Operating System :: OS Independent',
81 | 'Programming Language :: Python',
82 | 'Programming Language :: Python :: 3',
83 | 'Topic :: Internet :: WWW/HTTP',
84 | ]
85 | )
86 |
87 | # (*) Please direct queries to the discussion group, rather than to me directly
88 | # Doing so helps ensure your question is helpful to other users.
89 | # Queries directly to my email are likely to receive a canned response.
90 | #
91 | # Many thanks for your understanding.
92 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Django REST framework
2 | site_url: http://www.django-rest-framework.org/
3 | site_description: Django REST framework - Web APIs for Django
4 |
5 | repo_url: https://github.com/tomchristie/django-rest-framework
6 |
7 | theme_dir: docs_theme
8 |
9 | pages:
10 | - ['index.md', 'Home']
11 | - ['tutorial/quickstart.md', 'Tutorial', 'Quickstart']
12 | - ['tutorial/1-serialization.md', 'Tutorial', '1 - Serialization']
13 | - ['tutorial/2-requests-and-responses.md', 'Tutorial', '2 - Requests and responses']
14 | - ['tutorial/3-class-based-views.md', 'Tutorial', '3 - Class based views']
15 | - ['tutorial/4-authentication-and-permissions.md', 'Tutorial', '4 - Authentication and permissions']
16 | - ['tutorial/5-relationships-and-hyperlinked-apis.md', 'Tutorial', '5 - Relationships and hyperlinked APIs']
17 | - ['tutorial/6-viewsets-and-routers.md', 'Tutorial', '6 - Viewsets and routers']
18 | - ['api-guide/requests.md', 'API Guide', 'Requests']
19 | - ['api-guide/responses.md', 'API Guide', 'Responses']
20 | - ['api-guide/views.md', 'API Guide', 'Views']
21 | - ['api-guide/generic-views.md', 'API Guide', 'Generic views']
22 | - ['api-guide/viewsets.md', 'API Guide', 'Viewsets']
23 | - ['api-guide/routers.md', 'API Guide', 'Routers']
24 | - ['api-guide/parsers.md', 'API Guide', 'Parsers']
25 | - ['api-guide/renderers.md', 'API Guide', 'Renderers']
26 | - ['api-guide/serializers.md', 'API Guide', 'Serializers']
27 | - ['api-guide/fields.md', 'API Guide', 'Serializer fields']
28 | - ['api-guide/relations.md', 'API Guide', 'Serializer relations']
29 | - ['api-guide/validators.md', 'API Guide', 'Validators']
30 | - ['api-guide/authentication.md', 'API Guide', 'Authentication']
31 | - ['api-guide/permissions.md', 'API Guide', 'Permissions']
32 | - ['api-guide/throttling.md', 'API Guide', 'Throttling']
33 | - ['api-guide/filtering.md', 'API Guide', 'Filtering']
34 | - ['api-guide/pagination.md', 'API Guide', 'Pagination']
35 | - ['api-guide/versioning.md', 'API Guide', 'Versioning']
36 | - ['api-guide/content-negotiation.md', 'API Guide', 'Content negotiation']
37 | - ['api-guide/metadata.md', 'API Guide', 'Metadata']
38 | - ['api-guide/format-suffixes.md', 'API Guide', 'Format suffixes']
39 | - ['api-guide/reverse.md', 'API Guide', 'Returning URLs']
40 | - ['api-guide/exceptions.md', 'API Guide', 'Exceptions']
41 | - ['api-guide/status-codes.md', 'API Guide', 'Status codes']
42 | - ['api-guide/testing.md', 'API Guide', 'Testing']
43 | - ['api-guide/settings.md', 'API Guide', 'Settings']
44 | - ['topics/documenting-your-api.md', 'Topics', 'Documenting your API']
45 | - ['topics/internationalization.md', 'Topics', 'Internationalization']
46 | - ['topics/ajax-csrf-cors.md', 'Topics', 'AJAX, CSRF & CORS']
47 | - ['topics/browser-enhancements.md', 'Topics',]
48 | - ['topics/browsable-api.md', 'Topics', 'The Browsable API']
49 | - ['topics/rest-hypermedia-hateoas.md', 'Topics', 'REST, Hypermedia & HATEOAS']
50 | - ['topics/third-party-resources.md', 'Topics', 'Third Party Resources']
51 | - ['topics/contributing.md', 'Topics', 'Contributing to REST framework']
52 | - ['topics/project-management.md', 'Topics', 'Project management']
53 | - ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement']
54 | - ['topics/3.1-announcement.md', 'Topics', '3.1 Announcement']
55 | - ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement']
56 | - ['topics/release-notes.md', 'Topics', 'Release Notes']
57 |
--------------------------------------------------------------------------------
/rest_framework/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 | from django.db import models
7 | from django.utils.encoding import force_text
8 | from django.utils.functional import Promise
9 | from rest_framework.compat import unicode_repr
10 | import re
11 |
12 |
13 | def manager_repr(value):
14 | model = value.model
15 | opts = model._meta
16 | for _, name, manager in opts.concrete_managers + opts.abstract_managers:
17 | if manager == value:
18 | return '%s.%s.all()' % (model._meta.object_name, name)
19 | return repr(value)
20 |
21 |
22 | def smart_repr(value):
23 | if isinstance(value, models.Manager):
24 | return manager_repr(value)
25 |
26 | if isinstance(value, Promise) and value._delegate_text:
27 | value = force_text(value)
28 |
29 | value = unicode_repr(value)
30 |
31 | # Representations like u'help text'
32 | # should simply be presented as 'help text'
33 | if value.startswith("u'") and value.endswith("'"):
34 | return value[1:]
35 |
36 | # Representations like
37 | #
38 | # Should be presented as
39 | #
40 | value = re.sub(' at 0x[0-9a-f]{4,32}>', '>', value)
41 |
42 | return value
43 |
44 |
45 | def field_repr(field, force_many=False):
46 | kwargs = field._kwargs
47 | if force_many:
48 | kwargs = kwargs.copy()
49 | kwargs['many'] = True
50 | kwargs.pop('child', None)
51 |
52 | arg_string = ', '.join([smart_repr(val) for val in field._args])
53 | kwarg_string = ', '.join([
54 | '%s=%s' % (key, smart_repr(val))
55 | for key, val in sorted(kwargs.items())
56 | ])
57 | if arg_string and kwarg_string:
58 | arg_string += ', '
59 |
60 | if force_many:
61 | class_name = force_many.__class__.__name__
62 | else:
63 | class_name = field.__class__.__name__
64 |
65 | return "%s(%s%s)" % (class_name, arg_string, kwarg_string)
66 |
67 |
68 | def serializer_repr(serializer, indent, force_many=None):
69 | ret = field_repr(serializer, force_many) + ':'
70 | indent_str = ' ' * indent
71 |
72 | if force_many:
73 | fields = force_many.fields
74 | else:
75 | fields = serializer.fields
76 |
77 | for field_name, field in fields.items():
78 | ret += '\n' + indent_str + field_name + ' = '
79 | if hasattr(field, 'fields'):
80 | ret += serializer_repr(field, indent + 1)
81 | elif hasattr(field, 'child'):
82 | ret += list_repr(field, indent + 1)
83 | elif hasattr(field, 'child_relation'):
84 | ret += field_repr(field.child_relation, force_many=field.child_relation)
85 | else:
86 | ret += field_repr(field)
87 |
88 | if serializer.validators:
89 | ret += '\n' + indent_str + 'class Meta:'
90 | ret += '\n' + indent_str + ' validators = ' + smart_repr(serializer.validators)
91 |
92 | return ret
93 |
94 |
95 | def list_repr(serializer, indent):
96 | child = serializer.child
97 | if hasattr(child, 'fields'):
98 | return serializer_repr(serializer, indent, force_many=child)
99 | return field_repr(serializer)
100 |
--------------------------------------------------------------------------------
/rest_framework/authtoken/south_migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from south.db import db
3 | from south.v2 import SchemaMigration
4 |
5 | try:
6 | from django.contrib.auth import get_user_model
7 | except ImportError: # django < 1.5
8 | from django.contrib.auth.models import User
9 | else:
10 | User = get_user_model()
11 |
12 |
13 | class Migration(SchemaMigration):
14 |
15 | def forwards(self, orm):
16 | # Adding model 'Token'
17 | db.create_table('authtoken_token', (
18 | ('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)),
19 | ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='auth_token', unique=True, to=orm['%s.%s' % (User._meta.app_label, User._meta.object_name)])),
20 | ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
21 | ))
22 | db.send_create_signal('authtoken', ['Token'])
23 |
24 | def backwards(self, orm):
25 | # Deleting model 'Token'
26 | db.delete_table('authtoken_token')
27 |
28 | models = {
29 | 'auth.group': {
30 | 'Meta': {'object_name': 'Group'},
31 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
32 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
33 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
34 | },
35 | 'auth.permission': {
36 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
37 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
38 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
39 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
40 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
41 | },
42 | "%s.%s" % (User._meta.app_label, User._meta.module_name): {
43 | 'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)},
44 | },
45 | 'authtoken.token': {
46 | 'Meta': {'object_name': 'Token'},
47 | 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
48 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
49 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'auth_token'", 'unique': 'True', 'to': "orm['%s.%s']" % (User._meta.app_label, User._meta.object_name)})
50 | },
51 | 'contenttypes.contenttype': {
52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
57 | }
58 | }
59 |
60 | complete_apps = ['authtoken']
61 |
--------------------------------------------------------------------------------
/docs/topics/browser-enhancements.md:
--------------------------------------------------------------------------------
1 | # Browser enhancements
2 |
3 | > "There are two noncontroversial uses for overloaded POST. The first is to *simulate* HTTP's uniform interface for clients like web browsers that don't support PUT or DELETE"
4 | >
5 | > — [RESTful Web Services][cite], Leonard Richardson & Sam Ruby.
6 |
7 | ## Browser based PUT, DELETE, etc...
8 |
9 | REST framework supports browser-based `PUT`, `DELETE` and other methods, by
10 | overloading `POST` requests using a hidden form field.
11 |
12 | Note that this is the same strategy as is used in [Ruby on Rails][rails].
13 |
14 | For example, given the following form:
15 |
16 |
19 |
20 | `request.method` would return `"DELETE"`.
21 |
22 | ## HTTP header based method overriding
23 |
24 | REST framework also supports method overriding via the semi-standard `X-HTTP-Method-Override` header. This can be useful if you are working with non-form content such as JSON and are working with an older web server and/or hosting provider that doesn't recognise particular HTTP methods such as `PATCH`. For example [Amazon Web Services ELB][aws_elb].
25 |
26 | To use it, make a `POST` request, setting the `X-HTTP-Method-Override` header.
27 |
28 | For example, making a `PATCH` request via `POST` in jQuery:
29 |
30 | $.ajax({
31 | url: '/myresource/',
32 | method: 'POST',
33 | headers: {'X-HTTP-Method-Override': 'PATCH'},
34 | ...
35 | });
36 |
37 | ## Browser based submission of non-form content
38 |
39 | Browser-based submission of content types other than form are supported by
40 | using form fields named `_content` and `_content_type`:
41 |
42 | For example, given the following form:
43 |
44 |
48 |
49 | `request.content_type` would return `"application/json"`, and
50 | `request.stream` would return `"{'count': 1}"`
51 |
52 | ## URL based accept headers
53 |
54 | REST framework can take `?accept=application/json` style URL parameters,
55 | which allow the `Accept` header to be overridden.
56 |
57 | This can be useful for testing the API from a web browser, where you don't
58 | have any control over what is sent in the `Accept` header.
59 |
60 | ## URL based format suffixes
61 |
62 | REST framework can take `?format=json` style URL parameters, which can be a
63 | useful shortcut for determining which content type should be returned from
64 | the view.
65 |
66 | This is a more concise than using the `accept` override, but it also gives
67 | you less control. (For example you can't specify any media type parameters)
68 |
69 | ## Doesn't HTML5 support PUT and DELETE forms?
70 |
71 | Nope. It was at one point intended to support `PUT` and `DELETE` forms, but
72 | was later [dropped from the spec][html5]. There remains
73 | [ongoing discussion][put_delete] about adding support for `PUT` and `DELETE`,
74 | as well as how to support content types other than form-encoded data.
75 |
76 | [cite]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260
77 | [rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
78 | [html5]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24
79 | [put_delete]: http://amundsen.com/examples/put-delete-forms/
80 | [aws_elb]: https://forums.aws.amazon.com/thread.jspa?messageID=400724
81 |
--------------------------------------------------------------------------------
/rest_framework/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 | from django.utils.six.moves.http_client import responses
9 | from django.template.response import SimpleTemplateResponse
10 | from django.utils import six
11 |
12 |
13 | class Response(SimpleTemplateResponse):
14 | """
15 | An HttpResponse that allows its data to be rendered into
16 | arbitrary media types.
17 | """
18 |
19 | def __init__(self, data=None, status=None,
20 | template_name=None, headers=None,
21 | exception=False, content_type=None):
22 | """
23 | Alters the init arguments slightly.
24 | For example, drop 'template_name', and instead use 'data'.
25 |
26 | Setting 'renderer' and 'media_type' will typically be deferred,
27 | For example being set automatically by the `APIView`.
28 | """
29 | super(Response, self).__init__(None, status=status)
30 | self.data = data
31 | self.template_name = template_name
32 | self.exception = exception
33 | self.content_type = content_type
34 |
35 | if headers:
36 | for name, value in six.iteritems(headers):
37 | self[name] = value
38 |
39 | @property
40 | def rendered_content(self):
41 | renderer = getattr(self, 'accepted_renderer', None)
42 | media_type = getattr(self, 'accepted_media_type', None)
43 | context = getattr(self, 'renderer_context', None)
44 |
45 | assert renderer, ".accepted_renderer not set on Response"
46 | assert media_type, ".accepted_media_type not set on Response"
47 | assert context, ".renderer_context not set on Response"
48 | context['response'] = self
49 |
50 | charset = renderer.charset
51 | content_type = self.content_type
52 |
53 | if content_type is None and charset is not None:
54 | content_type = "{0}; charset={1}".format(media_type, charset)
55 | elif content_type is None:
56 | content_type = media_type
57 | self['Content-Type'] = content_type
58 |
59 | ret = renderer.render(self.data, media_type, context)
60 | if isinstance(ret, six.text_type):
61 | assert charset, (
62 | 'renderer returned unicode, and did not specify '
63 | 'a charset value.'
64 | )
65 | return bytes(ret.encode(charset))
66 |
67 | if not ret:
68 | del self['Content-Type']
69 |
70 | return ret
71 |
72 | @property
73 | def status_text(self):
74 | """
75 | Returns reason text corresponding to our HTTP response status code.
76 | Provided for convenience.
77 | """
78 | # TODO: Deprecate and use a template tag instead
79 | # TODO: Status code text for RFC 6585 status codes
80 | return responses.get(self.status_code, '')
81 |
82 | def __getstate__(self):
83 | """
84 | Remove attributes from the response that shouldn't be cached.
85 | """
86 | state = super(Response, self).__getstate__()
87 | for key in (
88 | 'accepted_renderer', 'renderer_context', 'resolver_match',
89 | 'client', 'request', 'wsgi_request'
90 | ):
91 | if key in state:
92 | del state[key]
93 | state['_closable_objects'] = []
94 | return state
95 |
--------------------------------------------------------------------------------
/docs/api-guide/format-suffixes.md:
--------------------------------------------------------------------------------
1 | source: urlpatterns.py
2 |
3 | # Format suffixes
4 |
5 | > Section 6.2.1 does not say that content negotiation should be
6 | used all the time.
7 | >
8 | > — Roy Fielding, [REST discuss mailing list][cite]
9 |
10 | A common pattern for Web APIs is to use filename extensions on URLs to provide an endpoint for a given media type. For example, 'http://example.com/api/users.json' to serve a JSON representation.
11 |
12 | Adding format-suffix patterns to each individual entry in the URLconf for your API is error-prone and non-DRY, so REST framework provides a shortcut to adding these patterns to your URLConf.
13 |
14 | ## format_suffix_patterns
15 |
16 | **Signature**: format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None)
17 |
18 | Returns a URL pattern list which includes format suffix patterns appended to each of the URL patterns provided.
19 |
20 | Arguments:
21 |
22 | * **urlpatterns**: Required. A URL pattern list.
23 | * **suffix_required**: Optional. A boolean indicating if suffixes in the URLs should be optional or mandatory. Defaults to `False`, meaning that suffixes are optional by default.
24 | * **allowed**: Optional. A list or tuple of valid format suffixes. If not provided, a wildcard format suffix pattern will be used.
25 |
26 | Example:
27 |
28 | from rest_framework.urlpatterns import format_suffix_patterns
29 | from blog import views
30 |
31 | urlpatterns = [
32 | url(r'^/$', views.apt_root),
33 | url(r'^comments/$', views.comment_list),
34 | url(r'^comments/(?P[0-9]+)/$', views.comment_detail)
35 | ]
36 |
37 | urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
38 |
39 | When using `format_suffix_patterns`, you must make sure to add the `'format'` keyword argument to the corresponding views. For example:
40 |
41 | @api_view(('GET', 'POST'))
42 | def comment_list(request, format=None):
43 | # do stuff...
44 |
45 | Or with class based views:
46 |
47 | class CommentList(APIView):
48 | def get(self, request, format=None):
49 | # do stuff...
50 |
51 | def post(self, request, format=None):
52 | # do stuff...
53 |
54 | The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` setting.
55 |
56 | Also note that `format_suffix_patterns` does not support descending into `include` URL patterns.
57 |
58 | ### Using with `i18n_patterns`
59 |
60 | If using the `i18n_patterns` function provided by Django, as well as `format_suffix_patterns` you should make sure that the `i18n_patterns` function is applied as the final, or outermost function. For example:
61 |
62 | url patterns = [
63 | …
64 | ]
65 |
66 | urlpatterns = i18n_patterns(
67 | format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
68 | )
69 |
70 | ---
71 |
72 | ## Accept headers vs. format suffixes
73 |
74 | There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead.
75 |
76 | It is actually a misconception. For example, take the following quote from Roy Fielding discussing the relative merits of query parameter media-type indicators vs. file extension media-type indicators:
77 |
78 | “That's why I always prefer extensions. Neither choice has anything to do with REST.” — Roy Fielding, [REST discuss mailing list][cite2]
79 |
80 | The quote does not mention Accept headers, but it does make it clear that format suffixes should be considered an acceptable pattern.
81 |
82 | [cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857
83 | [cite2]: http://tech.groups.yahoo.com/group/rest-discuss/message/14844
84 |
--------------------------------------------------------------------------------
/tests/test_relations_generic.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.contrib.contenttypes.models import ContentType
3 | from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
4 | from django.db import models
5 | from django.test import TestCase
6 | from django.utils.encoding import python_2_unicode_compatible
7 | from rest_framework import serializers
8 |
9 |
10 | @python_2_unicode_compatible
11 | class Tag(models.Model):
12 | """
13 | Tags have a descriptive slug, and are attached to an arbitrary object.
14 | """
15 | tag = models.SlugField()
16 | content_type = models.ForeignKey(ContentType)
17 | object_id = models.PositiveIntegerField()
18 | tagged_item = GenericForeignKey('content_type', 'object_id')
19 |
20 | def __str__(self):
21 | return self.tag
22 |
23 |
24 | @python_2_unicode_compatible
25 | class Bookmark(models.Model):
26 | """
27 | A URL bookmark that may have multiple tags attached.
28 | """
29 | url = models.URLField()
30 | tags = GenericRelation(Tag)
31 |
32 | def __str__(self):
33 | return 'Bookmark: %s' % self.url
34 |
35 |
36 | @python_2_unicode_compatible
37 | class Note(models.Model):
38 | """
39 | A textual note that may have multiple tags attached.
40 | """
41 | text = models.TextField()
42 | tags = GenericRelation(Tag)
43 |
44 | def __str__(self):
45 | return 'Note: %s' % self.text
46 |
47 |
48 | class TestGenericRelations(TestCase):
49 | def setUp(self):
50 | self.bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/')
51 | Tag.objects.create(tagged_item=self.bookmark, tag='django')
52 | Tag.objects.create(tagged_item=self.bookmark, tag='python')
53 | self.note = Note.objects.create(text='Remember the milk')
54 | Tag.objects.create(tagged_item=self.note, tag='reminder')
55 |
56 | def test_generic_relation(self):
57 | """
58 | Test a relationship that spans a GenericRelation field.
59 | IE. A reverse generic relationship.
60 | """
61 |
62 | class BookmarkSerializer(serializers.ModelSerializer):
63 | tags = serializers.StringRelatedField(many=True)
64 |
65 | class Meta:
66 | model = Bookmark
67 | fields = ('tags', 'url')
68 |
69 | serializer = BookmarkSerializer(self.bookmark)
70 | expected = {
71 | 'tags': ['django', 'python'],
72 | 'url': 'https://www.djangoproject.com/'
73 | }
74 | self.assertEqual(serializer.data, expected)
75 |
76 | def test_generic_fk(self):
77 | """
78 | Test a relationship that spans a GenericForeignKey field.
79 | IE. A forward generic relationship.
80 | """
81 |
82 | class TagSerializer(serializers.ModelSerializer):
83 | tagged_item = serializers.StringRelatedField()
84 |
85 | class Meta:
86 | model = Tag
87 | fields = ('tag', 'tagged_item')
88 |
89 | serializer = TagSerializer(Tag.objects.all(), many=True)
90 | expected = [
91 | {
92 | 'tag': 'django',
93 | 'tagged_item': 'Bookmark: https://www.djangoproject.com/'
94 | },
95 | {
96 | 'tag': 'python',
97 | 'tagged_item': 'Bookmark: https://www.djangoproject.com/'
98 | },
99 | {
100 | 'tag': 'reminder',
101 | 'tagged_item': 'Note: Remember the milk'
102 | }
103 | ]
104 | self.assertEqual(serializer.data, expected)
105 |
--------------------------------------------------------------------------------
/docs/topics/rest-hypermedia-hateoas.md:
--------------------------------------------------------------------------------
1 | # REST, Hypermedia & HATEOAS
2 |
3 | > You keep using that word "REST". I do not think it means what you think it means.
4 | >
5 | > — Mike Amundsen, [REST fest 2012 keynote][cite].
6 |
7 | First off, the disclaimer. The name "Django REST framework" was decided back in early 2011 and was chosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs".
8 |
9 | If you are serious about designing a Hypermedia API, you should look to resources outside of this documentation to help inform your design choices.
10 |
11 | The following fall into the "required reading" category.
12 |
13 | * Roy Fielding's dissertation - [Architectural Styles and
14 | the Design of Network-based Software Architectures][dissertation].
15 | * Roy Fielding's "[REST APIs must be hypertext-driven][hypertext-driven]" blog post.
16 | * Leonard Richardson & Mike Amundsen's [RESTful Web APIs][restful-web-apis].
17 | * Mike Amundsen's [Building Hypermedia APIs with HTML5 and Node][building-hypermedia-apis].
18 | * Steve Klabnik's [Designing Hypermedia APIs][designing-hypermedia-apis].
19 | * The [Richardson Maturity Model][maturitymodel].
20 |
21 | For a more thorough background, check out Klabnik's [Hypermedia API reading list][readinglist].
22 |
23 | ## Building Hypermedia APIs with REST framework
24 |
25 | REST framework is an agnostic Web API toolkit. It does help guide you towards building well-connected APIs, and makes it easy to design appropriate media types, but it does not strictly enforce any particular design style.
26 |
27 | ## What REST framework provides.
28 |
29 | It is self evident that REST framework makes it possible to build Hypermedia APIs. The browsable API that it offers is built on HTML - the hypermedia language of the web.
30 |
31 | REST framework also includes [serialization] and [parser]/[renderer] components that make it easy to build appropriate media types, [hyperlinked relations][fields] for building well-connected systems, and great support for [content negotiation][conneg].
32 |
33 | ## What REST framework doesn't provide.
34 |
35 | What REST framework doesn't do is give you is machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
36 |
37 | [cite]: http://vimeo.com/channels/restfest/page:2
38 | [dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
39 | [hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
40 | [restful-web-apis]: http://restfulwebapis.org/
41 | [building-hypermedia-apis]: http://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578
42 | [designing-hypermedia-apis]: http://designinghypermediaapis.com/
43 | [restisover]: http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over
44 | [readinglist]: http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list
45 | [maturitymodel]: http://martinfowler.com/articles/richardsonMaturityModel.html
46 |
47 | [hal]: http://stateless.co/hal_specification.html
48 | [collection]: http://www.amundsen.com/media-types/collection/
49 | [json-api]: http://jsonapi.org/
50 | [microformats]: http://microformats.org/wiki/Main_Page
51 | [serialization]: ../api-guide/serializers.md
52 | [parser]: ../api-guide/parsers.md
53 | [renderer]: ../api-guide/renderers.md
54 | [fields]: ../api-guide/fields.md
55 | [conneg]: ../api-guide/content-negotiation.md
56 |
--------------------------------------------------------------------------------
/tests/test_parsers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 | from django import forms
5 | from django.core.files.uploadhandler import MemoryFileUploadHandler
6 | from django.test import TestCase
7 | from django.utils.six.moves import StringIO
8 | from rest_framework.exceptions import ParseError
9 | from rest_framework.parsers import FormParser, FileUploadParser
10 |
11 |
12 | class Form(forms.Form):
13 | field1 = forms.CharField(max_length=3)
14 | field2 = forms.CharField()
15 |
16 |
17 | class TestFormParser(TestCase):
18 | def setUp(self):
19 | self.string = "field1=abc&field2=defghijk"
20 |
21 | def test_parse(self):
22 | """ Make sure the `QueryDict` works OK """
23 | parser = FormParser()
24 |
25 | stream = StringIO(self.string)
26 | data = parser.parse(stream)
27 |
28 | self.assertEqual(Form(data).is_valid(), True)
29 |
30 |
31 | class TestFileUploadParser(TestCase):
32 | def setUp(self):
33 | class MockRequest(object):
34 | pass
35 | from io import BytesIO
36 | self.stream = BytesIO(
37 | "Test text file".encode('utf-8')
38 | )
39 | request = MockRequest()
40 | request.upload_handlers = (MemoryFileUploadHandler(),)
41 | request.META = {
42 | 'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt',
43 | 'HTTP_CONTENT_LENGTH': 14,
44 | }
45 | self.parser_context = {'request': request, 'kwargs': {}}
46 |
47 | def test_parse(self):
48 | """
49 | Parse raw file upload.
50 | """
51 | parser = FileUploadParser()
52 | self.stream.seek(0)
53 | data_and_files = parser.parse(self.stream, None, self.parser_context)
54 | file_obj = data_and_files.files['file']
55 | self.assertEqual(file_obj._size, 14)
56 |
57 | def test_parse_missing_filename(self):
58 | """
59 | Parse raw file upload when filename is missing.
60 | """
61 | parser = FileUploadParser()
62 | self.stream.seek(0)
63 | self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = ''
64 | with self.assertRaises(ParseError):
65 | parser.parse(self.stream, None, self.parser_context)
66 |
67 | def test_parse_missing_filename_multiple_upload_handlers(self):
68 | """
69 | Parse raw file upload with multiple handlers when filename is missing.
70 | Regression test for #2109.
71 | """
72 | parser = FileUploadParser()
73 | self.stream.seek(0)
74 | self.parser_context['request'].upload_handlers = (
75 | MemoryFileUploadHandler(),
76 | MemoryFileUploadHandler()
77 | )
78 | self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = ''
79 | with self.assertRaises(ParseError):
80 | parser.parse(self.stream, None, self.parser_context)
81 |
82 | def test_get_filename(self):
83 | parser = FileUploadParser()
84 | filename = parser.get_filename(self.stream, None, self.parser_context)
85 | self.assertEqual(filename, 'file.txt')
86 |
87 | def test_get_encoded_filename(self):
88 | parser = FileUploadParser()
89 |
90 | self.__replace_content_disposition('inline; filename*=utf-8\'\'ÀĥƦ.txt')
91 | filename = parser.get_filename(self.stream, None, self.parser_context)
92 | self.assertEqual(filename, 'ÀĥƦ.txt')
93 |
94 | self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8\'\'ÀĥƦ.txt')
95 | filename = parser.get_filename(self.stream, None, self.parser_context)
96 | self.assertEqual(filename, 'ÀĥƦ.txt')
97 |
98 | self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8\'en-us\'ÀĥƦ.txt')
99 | filename = parser.get_filename(self.stream, None, self.parser_context)
100 | self.assertEqual(filename, 'ÀĥƦ.txt')
101 |
102 | def __replace_content_disposition(self, disposition):
103 | self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition
104 |
--------------------------------------------------------------------------------
/rest_framework/templates/rest_framework/login_base.html:
--------------------------------------------------------------------------------
1 | {% extends "rest_framework/base.html" %}
2 | {% load url from future %}
3 | {% load staticfiles %}
4 | {% load rest_framework %}
5 |
6 | {% block body %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {% block branding %}
Django REST framework
{% endblock %}
15 |
16 |
17 |
18 |
64 |
65 |
66 |
67 |
68 | {% endblock %}
69 |
--------------------------------------------------------------------------------
/docs/api-guide/metadata.md:
--------------------------------------------------------------------------------
1 | source: metadata.py
2 |
3 | # Metadata
4 |
5 | > [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
6 | >
7 | > — [RFC7231, Section 4.3.7.][cite]
8 |
9 | REST framework includes a configurable mechanism for determining how your API should respond to `OPTIONS` requests. This allows you to return API schema or other resource information.
10 |
11 | There are not currently any widely adopted conventions for exactly what style of response should be returned for HTTP `OPTIONS` requests, so we provide an ad-hoc style that returns some useful information.
12 |
13 | Here's an example response that demonstrates the information that is returned by default.
14 |
15 | HTTP 200 OK
16 | Allow: GET, POST, HEAD, OPTIONS
17 | Content-Type: application/json
18 |
19 | {
20 | "name": "To Do List",
21 | "description": "List existing 'To Do' items, or create a new item.",
22 | "renders": [
23 | "application/json",
24 | "text/html"
25 | ],
26 | "parses": [
27 | "application/json",
28 | "application/x-www-form-urlencoded",
29 | "multipart/form-data"
30 | ],
31 | "actions": {
32 | "POST": {
33 | "note": {
34 | "type": "string",
35 | "required": false,
36 | "read_only": false,
37 | "label": "title",
38 | "max_length": 100
39 | }
40 | }
41 | }
42 | }
43 |
44 | ## Setting the metadata scheme
45 |
46 | You can set the metadata class globally using the `'DEFAULT_METADATA_CLASS'` settings key:
47 |
48 | REST_FRAMEWORK = {
49 | 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata'
50 | }
51 |
52 | Or you can set the metadata class individually for a view:
53 |
54 | class APIRoot(APIView):
55 | metadata_class = APIRootMetadata
56 |
57 | def get(self, request, format=None):
58 | return Response({
59 | ...
60 | })
61 |
62 | The REST framework package only includes a single metadata class implementation, named `SimpleMetadata`. If you want to use an alternative style you'll need to implement a custom metadata class.
63 |
64 | ## Creating schema endpoints
65 |
66 | If you have specific requirements for creating schema endpoints that are accessed with regular `GET` requests, you might consider re-using the metadata API for doing so.
67 |
68 | For example, the following additional route could be used on a viewset to provide a linkable schema endpoint.
69 |
70 | @list_route(methods=['GET'])
71 | def schema(self, request):
72 | meta = self.metadata_class()
73 | data = meta.determine_metadata(request, self)
74 | return Response(data)
75 |
76 | There are a couple of reasons that you might choose to take this approach, including that `OPTIONS` responses [are not cacheable][no-options].
77 |
78 | ---
79 |
80 | # Custom metadata classes
81 |
82 | If you want to provide a custom metadata class you should override `BaseMetadata` and implement the `determine_metadata(self, request, view)` method.
83 |
84 | Useful things that you might want to do could include returning schema information, using a format such as [JSON schema][json-schema], or returning debug information to admin users.
85 |
86 | ## Example
87 |
88 | The following class could be used to limit the information that is returned to `OPTIONS` requests.
89 |
90 | class MinimalMetadata(BaseMetadata):
91 | """
92 | Don't include field and other information for `OPTIONS` requests.
93 | Just return the name and description.
94 | """
95 | def determine_metadata(self, request, view):
96 | return {
97 | 'name': view.get_view_name(),
98 | 'description': view.get_view_description()
99 | }
100 |
101 | [cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7
102 | [no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
103 | [json-schema]: http://json-schema.org/
104 |
--------------------------------------------------------------------------------
/rest_framework/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 | z-index: 3;
36 | }
37 |
38 | .navbar {
39 | background: #2C2C2C;
40 | color: white;
41 | border: none;
42 | border-top: 5px solid #A30000;
43 | border-radius: 0px;
44 | }
45 |
46 | .navbar .nav li, .navbar .nav li a, .navbar .brand:hover {
47 | color: white;
48 | }
49 |
50 | .nav-list > .active > a, .nav-list > .active > a:hover {
51 | background: #2C2C2C;
52 | }
53 |
54 | .navbar .dropdown-menu li a, .navbar .dropdown-menu li {
55 | color: #A30000;
56 | }
57 |
58 | .navbar .dropdown-menu li a:hover {
59 | background: #EEEEEE;
60 | color: #C20000;
61 | }
62 |
63 | ul.breadcrumb {
64 | margin: 70px 0 0 0;
65 | }
66 |
67 | .breadcrumb li.active a {
68 | color: #777;
69 | }
70 |
71 | .pagination>.disabled>a,
72 | .pagination>.disabled>a:hover,
73 | .pagination>.disabled>a:focus {
74 | cursor: not-allowed;
75 | pointer-events: none;
76 | }
77 |
78 | .pager>.disabled>a,
79 | .pager>.disabled>a:hover,
80 | .pager>.disabled>a:focus {
81 | pointer-events: none;
82 | }
83 |
84 | .pager .next {
85 | margin-left: 10px;
86 | }
87 |
88 | /*=== dabapps bootstrap styles ====*/
89 |
90 | html {
91 | width:100%;
92 | background: none;
93 | }
94 |
95 | /*body, .navbar .container-fluid {
96 | max-width: 1150px;
97 | margin: 0 auto;
98 | }*/
99 |
100 | body {
101 | background: url("../img/grid.png") repeat-x;
102 | background-attachment: fixed;
103 | }
104 |
105 | #content {
106 | margin: 0;
107 | padding-bottom: 60px;
108 | }
109 |
110 | /* sticky footer and footer */
111 | html, body {
112 | height: 100%;
113 | }
114 |
115 | .wrapper {
116 | position: relative;
117 | top: 0;
118 | left: 0;
119 | padding-top: 60px;
120 | margin: -60px 0;
121 | min-height: 100%;
122 | }
123 |
124 | .form-switcher {
125 | margin-bottom: 0;
126 | }
127 |
128 | .well {
129 | -webkit-box-shadow: none;
130 | -moz-box-shadow: none;
131 | box-shadow: none;
132 | }
133 |
134 | .well .form-actions {
135 | padding-bottom: 0;
136 | margin-bottom: 0;
137 | }
138 |
139 | .well form {
140 | margin-bottom: 0;
141 | }
142 |
143 | .nav-tabs {
144 | border: 0;
145 | }
146 |
147 | .nav-tabs > li {
148 | float: right;
149 | }
150 |
151 | .nav-tabs li a {
152 | margin-right: 0;
153 | }
154 |
155 | .nav-tabs > .active > a {
156 | background: #F5F5F5;
157 | }
158 |
159 | .nav-tabs > .active > a:hover {
160 | background: #F5F5F5;
161 | }
162 |
163 | .tabbable.first-tab-active .tab-content {
164 | border-top-right-radius: 0;
165 | }
166 |
167 | footer {
168 | position: absolute;
169 | bottom: 0;
170 | left: 0;
171 | clear: both;
172 | z-index: 10;
173 | height: 60px;
174 | width: 95%;
175 | margin: 0 2.5%;
176 | }
177 |
178 | footer p {
179 | text-align: center;
180 | color: gray;
181 | border-top: 1px solid #DDDDDD;
182 | padding-top: 10px;
183 | }
184 |
185 | footer a {
186 | color: gray !important;
187 | font-weight: bold;
188 | }
189 |
190 | footer a:hover {
191 | color: gray;
192 | }
193 |
194 | .page-header {
195 | border-bottom: none;
196 | padding-bottom: 0px;
197 | margin: 0;
198 | }
199 |
200 | /* custom general page styles */
201 | .hero-unit h1, .hero-unit h2 {
202 | color: #A30000;
203 | }
204 |
205 | body a {
206 | color: #A30000;
207 | }
208 |
209 | body a:hover {
210 | color: #c20000;
211 | }
212 |
213 | .request-info {
214 | clear:both;
215 | }
216 |
--------------------------------------------------------------------------------
/tests/test_description.py:
--------------------------------------------------------------------------------
1 | # -- coding: utf-8 --
2 |
3 | from __future__ import unicode_literals
4 | from django.test import TestCase
5 | from django.utils.encoding import python_2_unicode_compatible, smart_text
6 | from rest_framework.compat import apply_markdown
7 | from rest_framework.views import APIView
8 | from .description import ViewWithNonASCIICharactersInDocstring
9 | from .description import UTF8_TEST_DOCSTRING
10 |
11 | # We check that docstrings get nicely un-indented.
12 | DESCRIPTION = """an example docstring
13 | ====================
14 |
15 | * list
16 | * list
17 |
18 | another header
19 | --------------
20 |
21 | code block
22 |
23 | indented
24 |
25 | # hash style header #"""
26 |
27 | # If markdown is installed we also test it's working
28 | # (and that our wrapped forces '=' to h2 and '-' to h3)
29 |
30 | # We support markdown < 2.1 and markdown >= 2.1
31 | MARKED_DOWN_lt_21 = """an example docstring
32 |
36 | another header
37 | code block
38 |
39 | indented
40 | """
41 |
42 | MARKED_DOWN_gte_21 = """an example docstring
43 |
47 |
48 | code block
49 |
50 | indented
51 | """
52 |
53 |
54 | class TestViewNamesAndDescriptions(TestCase):
55 | def test_view_name_uses_class_name(self):
56 | """
57 | Ensure view names are based on the class name.
58 | """
59 | class MockView(APIView):
60 | pass
61 | self.assertEqual(MockView().get_view_name(), 'Mock')
62 |
63 | def test_view_description_uses_docstring(self):
64 | """Ensure view descriptions are based on the docstring."""
65 | class MockView(APIView):
66 | """an example docstring
67 | ====================
68 |
69 | * list
70 | * list
71 |
72 | another header
73 | --------------
74 |
75 | code block
76 |
77 | indented
78 |
79 | # hash style header #"""
80 |
81 | self.assertEqual(MockView().get_view_description(), DESCRIPTION)
82 |
83 | def test_view_description_supports_unicode(self):
84 | """
85 | Unicode in docstrings should be respected.
86 | """
87 |
88 | self.assertEqual(
89 | ViewWithNonASCIICharactersInDocstring().get_view_description(),
90 | smart_text(UTF8_TEST_DOCSTRING)
91 | )
92 |
93 | def test_view_description_can_be_empty(self):
94 | """
95 | Ensure that if a view has no docstring,
96 | then it's description is the empty string.
97 | """
98 | class MockView(APIView):
99 | pass
100 | self.assertEqual(MockView().get_view_description(), '')
101 |
102 | def test_view_description_can_be_promise(self):
103 | """
104 | Ensure a view may have a docstring that is actually a lazily evaluated
105 | class that can be converted to a string.
106 |
107 | See: https://github.com/tomchristie/django-rest-framework/issues/1708
108 | """
109 | # use a mock object instead of gettext_lazy to ensure that we can't end
110 | # up with a test case string in our l10n catalog
111 | @python_2_unicode_compatible
112 | class MockLazyStr(object):
113 | def __init__(self, string):
114 | self.s = string
115 |
116 | def __str__(self):
117 | return self.s
118 |
119 | class MockView(APIView):
120 | __doc__ = MockLazyStr("a gettext string")
121 |
122 | self.assertEqual(MockView().get_view_description(), 'a gettext string')
123 |
124 | def test_markdown(self):
125 | """
126 | Ensure markdown to HTML works as expected.
127 | """
128 | if apply_markdown:
129 | gte_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_gte_21
130 | lt_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_lt_21
131 | self.assertTrue(gte_21_match or lt_21_match)
132 |
--------------------------------------------------------------------------------
/rest_framework/utils/serializer_helpers.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | import collections
3 | from rest_framework.compat import OrderedDict, unicode_to_repr
4 |
5 |
6 | class ReturnDict(OrderedDict):
7 | """
8 | Return object from `serialier.data` for the `Serializer` class.
9 | Includes a backlink to the serializer instance for renderers
10 | to use if they need richer field information.
11 | """
12 | def __init__(self, *args, **kwargs):
13 | self.serializer = kwargs.pop('serializer')
14 | super(ReturnDict, self).__init__(*args, **kwargs)
15 |
16 | def copy(self):
17 | return ReturnDict(self, serializer=self.serializer)
18 |
19 | def __repr__(self):
20 | return dict.__repr__(self)
21 |
22 | def __reduce__(self):
23 | # Pickling these objects will drop the .serializer backlink,
24 | # but preserve the raw data.
25 | return (dict, (dict(self),))
26 |
27 |
28 | class ReturnList(list):
29 | """
30 | Return object from `serialier.data` for the `SerializerList` class.
31 | Includes a backlink to the serializer instance for renderers
32 | to use if they need richer field information.
33 | """
34 | def __init__(self, *args, **kwargs):
35 | self.serializer = kwargs.pop('serializer')
36 | super(ReturnList, self).__init__(*args, **kwargs)
37 |
38 | def __repr__(self):
39 | return list.__repr__(self)
40 |
41 | def __reduce__(self):
42 | # Pickling these objects will drop the .serializer backlink,
43 | # but preserve the raw data.
44 | return (list, (list(self),))
45 |
46 |
47 | class BoundField(object):
48 | """
49 | A field object that also includes `.value` and `.error` properties.
50 | Returned when iterating over a serializer instance,
51 | providing an API similar to Django forms and form fields.
52 | """
53 | def __init__(self, field, value, errors, prefix=''):
54 | self._field = field
55 | self.value = value
56 | self.errors = errors
57 | self.name = prefix + self.field_name
58 |
59 | def __getattr__(self, attr_name):
60 | return getattr(self._field, attr_name)
61 |
62 | @property
63 | def _proxy_class(self):
64 | return self._field.__class__
65 |
66 | def __repr__(self):
67 | return unicode_to_repr('<%s value=%s errors=%s>' % (
68 | self.__class__.__name__, self.value, self.errors
69 | ))
70 |
71 |
72 | class NestedBoundField(BoundField):
73 | """
74 | This `BoundField` additionally implements __iter__ and __getitem__
75 | in order to support nested bound fields. This class is the type of
76 | `BoundField` that is used for serializer fields.
77 | """
78 | def __iter__(self):
79 | for field in self.fields.values():
80 | yield self[field.field_name]
81 |
82 | def __getitem__(self, key):
83 | field = self.fields[key]
84 | value = self.value.get(key) if self.value else None
85 | error = self.errors.get(key) if self.errors else None
86 | if hasattr(field, 'fields'):
87 | return NestedBoundField(field, value, error, prefix=self.name + '.')
88 | return BoundField(field, value, error, prefix=self.name + '.')
89 |
90 |
91 | class BindingDict(collections.MutableMapping):
92 | """
93 | This dict-like object is used to store fields on a serializer.
94 |
95 | This ensures that whenever fields are added to the serializer we call
96 | `field.bind()` so that the `field_name` and `parent` attributes
97 | can be set correctly.
98 | """
99 | def __init__(self, serializer):
100 | self.serializer = serializer
101 | self.fields = OrderedDict()
102 |
103 | def __setitem__(self, key, field):
104 | self.fields[key] = field
105 | field.bind(field_name=key, parent=self.serializer)
106 |
107 | def __getitem__(self, key):
108 | return self.fields[key]
109 |
110 | def __delitem__(self, key):
111 | del self.fields[key]
112 |
113 | def __iter__(self):
114 | return iter(self.fields)
115 |
116 | def __len__(self):
117 | return len(self.fields)
118 |
119 | def __repr__(self):
120 | return dict.__repr__(self.fields)
121 |
--------------------------------------------------------------------------------
/tests/test_serializer_bulk_update.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests to cover bulk create and update using serializers.
3 | """
4 | from __future__ import unicode_literals
5 | from django.test import TestCase
6 | from django.utils import six
7 | from rest_framework import serializers
8 |
9 |
10 | class BulkCreateSerializerTests(TestCase):
11 | """
12 | Creating multiple instances using serializers.
13 | """
14 |
15 | def setUp(self):
16 | class BookSerializer(serializers.Serializer):
17 | id = serializers.IntegerField()
18 | title = serializers.CharField(max_length=100)
19 | author = serializers.CharField(max_length=100)
20 |
21 | self.BookSerializer = BookSerializer
22 |
23 | def test_bulk_create_success(self):
24 | """
25 | Correct bulk update serialization should return the input data.
26 | """
27 |
28 | data = [
29 | {
30 | 'id': 0,
31 | 'title': 'The electric kool-aid acid test',
32 | 'author': 'Tom Wolfe'
33 | }, {
34 | 'id': 1,
35 | 'title': 'If this is a man',
36 | 'author': 'Primo Levi'
37 | }, {
38 | 'id': 2,
39 | 'title': 'The wind-up bird chronicle',
40 | 'author': 'Haruki Murakami'
41 | }
42 | ]
43 |
44 | serializer = self.BookSerializer(data=data, many=True)
45 | self.assertEqual(serializer.is_valid(), True)
46 | self.assertEqual(serializer.validated_data, data)
47 |
48 | def test_bulk_create_errors(self):
49 | """
50 | Incorrect bulk create serialization should return errors.
51 | """
52 |
53 | data = [
54 | {
55 | 'id': 0,
56 | 'title': 'The electric kool-aid acid test',
57 | 'author': 'Tom Wolfe'
58 | }, {
59 | 'id': 1,
60 | 'title': 'If this is a man',
61 | 'author': 'Primo Levi'
62 | }, {
63 | 'id': 'foo',
64 | 'title': 'The wind-up bird chronicle',
65 | 'author': 'Haruki Murakami'
66 | }
67 | ]
68 | expected_errors = [
69 | {},
70 | {},
71 | {'id': ['A valid integer is required.']}
72 | ]
73 |
74 | serializer = self.BookSerializer(data=data, many=True)
75 | self.assertEqual(serializer.is_valid(), False)
76 | self.assertEqual(serializer.errors, expected_errors)
77 |
78 | def test_invalid_list_datatype(self):
79 | """
80 | Data containing list of incorrect data type should return errors.
81 | """
82 | data = ['foo', 'bar', 'baz']
83 | serializer = self.BookSerializer(data=data, many=True)
84 | self.assertEqual(serializer.is_valid(), False)
85 |
86 | text_type_string = six.text_type.__name__
87 | message = 'Invalid data. Expected a dictionary, but got %s.' % text_type_string
88 | expected_errors = [
89 | {'non_field_errors': [message]},
90 | {'non_field_errors': [message]},
91 | {'non_field_errors': [message]}
92 | ]
93 |
94 | self.assertEqual(serializer.errors, expected_errors)
95 |
96 | def test_invalid_single_datatype(self):
97 | """
98 | Data containing a single incorrect data type should return errors.
99 | """
100 | data = 123
101 | serializer = self.BookSerializer(data=data, many=True)
102 | self.assertEqual(serializer.is_valid(), False)
103 |
104 | expected_errors = {'non_field_errors': ['Expected a list of items but got type "int".']}
105 |
106 | self.assertEqual(serializer.errors, expected_errors)
107 |
108 | def test_invalid_single_object(self):
109 | """
110 | Data containing only a single object, instead of a list of objects
111 | should return errors.
112 | """
113 | data = {
114 | 'id': 0,
115 | 'title': 'The electric kool-aid acid test',
116 | 'author': 'Tom Wolfe'
117 | }
118 | serializer = self.BookSerializer(data=data, many=True)
119 | self.assertEqual(serializer.is_valid(), False)
120 |
121 | expected_errors = {'non_field_errors': ['Expected a list of items but got type "dict".']}
122 |
123 | self.assertEqual(serializer.errors, expected_errors)
124 |
--------------------------------------------------------------------------------
/rest_framework/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 | from django.http import Http404
7 | from rest_framework import HTTP_HEADER_ENCODING, exceptions
8 | from rest_framework.settings import api_settings
9 | from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
10 | from rest_framework.utils.mediatypes import _MediaType
11 |
12 |
13 | class BaseContentNegotiation(object):
14 | def select_parser(self, request, parsers):
15 | raise NotImplementedError('.select_parser() must be implemented')
16 |
17 | def select_renderer(self, request, renderers, format_suffix=None):
18 | raise NotImplementedError('.select_renderer() must be implemented')
19 |
20 |
21 | class DefaultContentNegotiation(BaseContentNegotiation):
22 | settings = api_settings
23 |
24 | def select_parser(self, request, parsers):
25 | """
26 | Given a list of parsers and a media type, return the appropriate
27 | parser to handle the incoming request.
28 | """
29 | for parser in parsers:
30 | if media_type_matches(parser.media_type, request.content_type):
31 | return parser
32 | return None
33 |
34 | def select_renderer(self, request, renderers, format_suffix=None):
35 | """
36 | Given a request and a list of renderers, return a two-tuple of:
37 | (renderer, media type).
38 | """
39 | # Allow URL style format override. eg. "?format=json
40 | format_query_param = self.settings.URL_FORMAT_OVERRIDE
41 | format = format_suffix or request.query_params.get(format_query_param)
42 |
43 | if format:
44 | renderers = self.filter_renderers(renderers, format)
45 |
46 | accepts = self.get_accept_list(request)
47 |
48 | # Check the acceptable media types against each renderer,
49 | # attempting more specific media types first
50 | # NB. The inner loop here isn't as bad as it first looks :)
51 | # Worst case is we're looping over len(accept_list) * len(self.renderers)
52 | for media_type_set in order_by_precedence(accepts):
53 | for renderer in renderers:
54 | for media_type in media_type_set:
55 | if media_type_matches(renderer.media_type, media_type):
56 | # Return the most specific media type as accepted.
57 | media_type_wrapper = _MediaType(media_type)
58 | if (
59 | _MediaType(renderer.media_type).precedence >
60 | media_type_wrapper.precedence
61 | ):
62 | # Eg client requests '*/*'
63 | # Accepted media type is 'application/json'
64 | full_media_type = ';'.join(
65 | (renderer.media_type,) +
66 | tuple('{0}={1}'.format(
67 | key, value.decode(HTTP_HEADER_ENCODING))
68 | for key, value in media_type_wrapper.params.items()))
69 | return renderer, full_media_type
70 | else:
71 | # Eg client requests 'application/json; indent=8'
72 | # Accepted media type is 'application/json; indent=8'
73 | return renderer, media_type
74 |
75 | raise exceptions.NotAcceptable(available_renderers=renderers)
76 |
77 | def filter_renderers(self, renderers, format):
78 | """
79 | If there is a '.json' style format suffix, filter the renderers
80 | so that we only negotiation against those that accept that format.
81 | """
82 | renderers = [renderer for renderer in renderers
83 | if renderer.format == format]
84 | if not renderers:
85 | raise Http404
86 | return renderers
87 |
88 | def get_accept_list(self, request):
89 | """
90 | Given the incoming request, return a tokenised list of media
91 | type strings.
92 |
93 | Allows URL style accept override. eg. "?accept=application/json"
94 | """
95 | header = request.META.get('HTTP_ACCEPT', '*/*')
96 | header = request.query_params.get(self.settings.URL_ACCEPT_OVERRIDE, header)
97 | return [token.strip() for token in header.split(',')]
98 |
--------------------------------------------------------------------------------