diff -Nru django-tastypie-0.13.3/AUTHORS django-tastypie-0.14.3/AUTHORS --- django-tastypie-0.13.3/AUTHORS 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/AUTHORS 2019-12-09 15:31:52.000000000 +0000 @@ -36,7 +36,7 @@ * Ed Summers (edsu) for a setup.py patch. * Sébastien Fievet (zyegfryed) for the initial OAuth implementation. * Jacob Kaplan-Moss (jacobkm) for the PATCH patch. -* jorgeecardona for a patch in renaming the ``objects`` name of the response. +* Jorge E. Cardona (jorgeecardona) for a patch in renaming the ``objects`` name of the response, and get_multiple in one query. * vbabiy for a patch on improved use of ``bundle.request`` & related resource validation. * philipn (Philip Neustrom) for GeoDjango integration. * dgerzo (Daniel Gerzo) for GeoDjango integration, work on PATCH and related fields, improving the ``run_all_tests.sh`` script & several smaller patches.. @@ -98,7 +98,12 @@ * Mike Bryant (mikebryant) for unit tests * Maxim Filipenko (prokaktus) for changing repr value to str in Python 2 * Thomas Yip (thomasyip) PR #1409 -* Sam Thompson(georgedorn) for fixing some docs and tests. +* Matt Cooper (vtbassmatt) for making ApiKey better in Python 3 +* Nick Sullivan (gorillamania) for admin improvements. +* Danny Roberts (dannyroberts) for very small change addressing noisy RemovedInDjango20Warning warnings +* Sam Thompson (georgedorn) for ongoing maintenance and release management. +* Matt Briançon (mattbriancon) for various patches + Thanks to Tav for providing validate_jsonp.py, placed in public domain. diff -Nru django-tastypie-0.13.3/BACKWARDS-INCOMPATIBLE.txt django-tastypie-0.14.3/BACKWARDS-INCOMPATIBLE.txt --- django-tastypie-0.13.3/BACKWARDS-INCOMPATIBLE.txt 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/BACKWARDS-INCOMPATIBLE.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -Master (v0.9.16) -================ - -[2012-12-11] abc0bef - Changed response code of PUT with always_return_data=True from 202 to 200 - - -v0.9.13 -======= - -[2013-03-15] 2dff249 - Authorization methods that were previously expected to raise ``Unauthorized`` must now always return ``True`` if the authorization succeeds - - -v0.9.12 -======= - -[2013-02-14] 76c4f15 - JSONP is disabled by default -[2013-02-14] 3ee47fb - Tastypie no longer ever returns HTML errors -[2013-02-06] d850758 - Heavy rewrite of ``Authorization`` which adds a variety of new methods -[2012-08-04] 651f964 - The addition of ``SessionAuthentication`` requires Django 1.3+ -[2012-06-26] a57d85b - ``Authentication`` classes now require ``User.is_active = True`` -[2012-05-04] 84821d7 - AutoFields will now serialize and deserialize as Integers - - -Backward-incompatible commits were not recorded prior to the release of v0.9.11 -but may be found using ``git log --oneline ...v0.9.11 | grep "BACKWARD-"``. diff -Nru django-tastypie-0.13.3/CONTRIBUTING django-tastypie-0.14.3/CONTRIBUTING --- django-tastypie-0.13.3/CONTRIBUTING 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/CONTRIBUTING 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ -============ -Contributing -============ - -Tastypie is open-source and, as such, grows (or shrinks) & improves in part -due to the community. Below are some guidelines on how to help with the project. - - -Philosophy -========== - -* Tastypie is BSD-licensed. All contributed code must be either - - * the original work of the author, contributed under the BSD, or... - * work taken from another project released under a BSD-compatible license. - -* GPL'd (or similar) works are not eligible for inclusion. -* Tastypie's git master branch should always be stable, production-ready & - passing all tests. -* Major releases (1.x.x) are commitments to backward-compatibility of the public APIs. - Any documented API should ideally not change between major releases. - The exclusion to this rule is in the event of either a security issue - or to accommodate changes in Django itself. -* Minor releases (x.3.x) are for the addition of substantial features or major - bugfixes. -* Patch releases (x.x.4) are for minor features or bugfixes. - - -Guidelines For Reporting An Issue/Feature -========================================= - -So you've found a bug or have a great idea for a feature. Here's the steps you -should take to help get it added/fixed in Tastypie: - -* First, check to see if there's an existing issue/pull request for the - bug/feature. All issues are at https://github.com/django-tastypie/django-tastypie/issues - and pull reqs are at https://github.com/django-tastypie/django-tastypie/pulls. -* If there isn't one there, please file an issue. The ideal report includes: - - * A description of the problem/suggestion. - * How to recreate the bug. - * If relevant, including the versions of your: - - * Python interpreter - * Django - * Tastypie - * Optionally of the other dependencies involved - - * Ideally, creating a pull request with a (failing) test case demonstrating - what's wrong. This makes it easy for us to reproduce & fix the problem. - Instructions for running the tests are at :doc:`index` - -You might also hop into the IRC channel (``#tastypie`` on ``irc.freenode.net``) -& raise your question there, as there may be someone who can help you with a -work-around. - - -Guidelines For Contributing Code -================================ - -If you're ready to take the plunge & contribute back some code/docs, the -process should look like: - -* Fork the project on GitHub into your own account. -* Clone your copy of Tastypie. -* Make a new branch in git & commit your changes there. -* Push your new branch up to GitHub. -* Again, ensure there isn't already an issue or pull request out there on it. - If there is & you feel you have a better fix, please take note of the issue - number & mention it in your pull request. -* Create a new pull request (based on your branch), including what the - problem/feature is, versions of your software & referencing any related - issues/pull requests. - -In order to be merged into Tastypie, contributions must have the following: - -* A solid patch that: - - * is clear. - * works across all supported versions of Python/Django. - * follows the existing style of the code base (mostly PEP-8). - * comments included as needed. - -* A test case that demonstrates the previous flaw that now passes - with the included patch. -* If it adds/changes a public API, it must also include documentation - for those changes. -* Must be appropriately licensed (see "Philosophy"). -* Adds yourself to the AUTHORS file. - -Please also: - -* Unless your change only modifies the documentation, add the issue you're - solving to the list in docs/release_notes/dev.rst, include issue and PR - numbers. -* Squash your changes down to a single commit, or down to one commit containing - your failing tests and one more commit containing the fix that makes those - tests pass. - -If your contribution lacks any of these things, they will have to be added -by a core contributor before being merged into Tastypie proper, which may take -substantial time for the all-volunteer team to get to. - - -Guidelines For Core Contributors -================================ - -If you've been granted the commit bit, here's how to shepherd the changes in: - -* Any time you go to work on Tastypie, please use ``git pull --rebase`` to fetch - the latest changes. -* Any new features/bug fixes must meet the above guidelines for contributing - code (solid patch/tests passing/docs included). -* Commits are typically cherry-picked onto a branch off master. - - * This is done so as not to include extraneous commits, as some people submit - pull reqs based on their git master that has other things applied to it. - -* A set of commits should be squashed down to a single commit. - - * ``git merge --squash`` is a good tool for performing this, as is - ``git rebase -i HEAD~N``. - * This is done to prevent anyone using the git repo from accidently pulling - work-in-progress commits. - -* Commit messages should use past tense, describe what changed & thank anyone - involved. Examples:: - - """Added a new way to do per-object authorization.""" - """Fixed a bug in ``Serializer.to_xml``. Thanks to joeschmoe for the report!""" - """BACKWARD-INCOMPATIBLE: Altered the arguments passed to ``Bundle.__init__``. - - Further description appears here if the change warrants an explanation - as to why it was done.""" - -* For any patches applied from a contributor, please ensure their name appears - in the AUTHORS file. -* When closing issues or pull requests, please reference the SHA in the closing - message (i.e. ``Thanks! Fixed in SHA: 6b93f6``). GitHub will automatically - link to it. diff -Nru django-tastypie-0.13.3/debian/changelog django-tastypie-0.14.3/debian/changelog --- django-tastypie-0.13.3/debian/changelog 2019-07-26 15:34:29.000000000 +0000 +++ django-tastypie-0.14.3/debian/changelog 2021-01-04 17:00:00.000000000 +0000 @@ -1,3 +1,9 @@ +django-tastypie (0.14.3-1~focal0) focal; urgency=medium + + * New upstream release. + + -- Angelos Tzotsos Mon, 04 Jan 2021 19:00:00 +0200 + django-tastypie (0.13.3-1.1) unstable; urgency=medium * Non-maintainer upload. diff -Nru django-tastypie-0.13.3/django_tastypie.egg-info/dependency_links.txt django-tastypie-0.14.3/django_tastypie.egg-info/dependency_links.txt --- django-tastypie-0.13.3/django_tastypie.egg-info/dependency_links.txt 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/django_tastypie.egg-info/dependency_links.txt 2020-01-06 14:47:05.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru django-tastypie-0.13.3/django_tastypie.egg-info/not-zip-safe django-tastypie-0.14.3/django_tastypie.egg-info/not-zip-safe --- django-tastypie-0.13.3/django_tastypie.egg-info/not-zip-safe 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/django_tastypie.egg-info/not-zip-safe 2017-07-03 14:39:08.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru django-tastypie-0.13.3/django_tastypie.egg-info/PKG-INFO django-tastypie-0.14.3/django_tastypie.egg-info/PKG-INFO --- django-tastypie-0.13.3/django_tastypie.egg-info/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/django_tastypie.egg-info/PKG-INFO 2020-01-06 14:47:05.000000000 +0000 @@ -0,0 +1,170 @@ +Metadata-Version: 1.1 +Name: django-tastypie +Version: 0.14.3 +Summary: A flexible & capable API layer for Django. +Home-page: https://github.com/django-tastypie/django-tastypie +Author: Daniel Lindsley +Author-email: daniel@toastdriven.com +License: UNKNOWN +Description: =============== + django-tastypie + =============== + + .. image:: https://readthedocs.org/projects/django-tastypie/badge/ + :target: https://django-tastypie.readthedocs.io/ + :alt: Docs + + .. image:: https://travis-ci.org/django-tastypie/django-tastypie.svg?branch=master + :target: https://travis-ci.org/django-tastypie/django-tastypie + :alt: CI + + .. image:: https://coveralls.io/repos/django-tastypie/django-tastypie/badge.svg?service=github + :target: https://coveralls.io/github/django-tastypie/django-tastypie + :alt: Code Coverage + + .. image:: https://img.shields.io/pypi/v/django-tastypie.svg + :target: https://pypi.python.org/pypi/django-tastypie + :alt: Version + + .. image:: https://pypi-badges.global.ssl.fastly.net/svg?package=django-tastypie&timeframe=monthly + :target: https://pypi.python.org/pypi/django-tastypie + :alt: Downloads + + Creating delicious APIs for Django apps since 2010. + + Currently in beta but being used actively in production on several + sites. + + + Requirements + ============ + + Core + ---- + + * Python 2.7+ or Python 3.4+ (Whatever is supported by your version of Django) + * Django 1.11, 2.2 (LTS releases) or Django 3.0 (latest release) + * dateutil (http://labix.org/python-dateutil) >= 2.1 + + Format Support + -------------- + + * XML: lxml 3 (http://lxml.de/) and defusedxml (https://pypi.python.org/pypi/defusedxml) + * YAML: pyyaml (http://pyyaml.org/) + * binary plist: biplist (https://bitbucket.org/wooster/biplist) + + Optional + -------- + + * HTTP Digest authentication: python3-digest (https://bitbucket.org/akoha/python-digest/) + + + What's It Look Like? + ==================== + + A basic example looks like: + + .. code:: python + + # myapp/api.py + # ============ + from tastypie.resources import ModelResource + from myapp.models import Entry + + + class EntryResource(ModelResource): + class Meta: + queryset = Entry.objects.all() + + + # urls.py + # ======= + from django.conf.urls import url, include + from tastypie.api import Api + from myapp.api import EntryResource + + v1_api = Api(api_name='v1') + v1_api.register(EntryResource()) + + urlpatterns = [ + # The normal jazz here then... + url(r'^api/', include(v1_api.urls)), + ] + + That gets you a fully working, read-write API for the ``Entry`` model that + supports all CRUD operations in a RESTful way. JSON/XML/YAML support is already + there, and it's easy to add related data/authentication/caching. + + You can find more in the documentation at + https://django-tastypie.readthedocs.io/. + + + Why Tastypie? + ============= + + There are other API frameworks out there for Django. You need to + assess the options available and decide for yourself. That said, here are some + common reasons for tastypie. + + * You need an API that is RESTful and uses HTTP well. + * You want to support deep relations. + * You DON'T want to have to write your own serializer to make the output right. + * You want an API framework that has little magic, very flexible and maps well to + the problem domain. + * You want/need XML serialization that is treated equally to JSON (and YAML is + there too). + + + Reference Material + ================== + + * https://django-tastypie.readthedocs.io/en/latest/ + * https://github.com/django-tastypie/django-tastypie/tree/master/tests/basic shows + basic usage of tastypie + * http://en.wikipedia.org/wiki/REST + * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + * http://www.ietf.org/rfc/rfc2616.txt + * http://jacobian.org/writing/rest-worst-practices/ + + + Getting Help + ============ + + There are two primary ways of getting help. + + 1. Go to `StackOverflow`_ and post a question with the ``tastypie`` tag. + 2. We have an IRC channel (`#tastypie on irc.freenode.net`_) to get help, + bounce an idea by us, or generally shoot the breeze. + + .. _`StackOverflow`: https://stackoverflow.com/questions/tagged/tastypie + .. _#tastypie on irc.freenode.net: irc://irc.freenode.net/tastypie + + + Security + ======== + + Tastypie is committed to providing a flexible and secure API, and was designed + with many security features and options in mind. Due to the complex nature of + APIs and the constant discovery of new attack vectors and vulnerabilities, + no software is immune to security holes. We rely on our community to report + and help us investigate security issues. + + If you come across a security hole **please do not open a Github issue**. + Instead, **drop us an email** at ``tastypie-security@googlegroups.com`` + + We'll then work together to investigate and resolve the problem so we can + announce a solution along with the vulnerability. + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Utilities +Requires: python_mimeparse(>=0.1.4, !=1.5) +Requires: dateutil(>=1.5, !=2.0) diff -Nru django-tastypie-0.13.3/django_tastypie.egg-info/requires.txt django-tastypie-0.14.3/django_tastypie.egg-info/requires.txt --- django-tastypie-0.13.3/django_tastypie.egg-info/requires.txt 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/django_tastypie.egg-info/requires.txt 2020-01-06 14:47:05.000000000 +0000 @@ -0,0 +1,2 @@ +python-mimeparse!=1.5,>=0.1.4 +python-dateutil!=2.0,>=1.5 diff -Nru django-tastypie-0.13.3/django_tastypie.egg-info/SOURCES.txt django-tastypie-0.14.3/django_tastypie.egg-info/SOURCES.txt --- django-tastypie-0.13.3/django_tastypie.egg-info/SOURCES.txt 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/django_tastypie.egg-info/SOURCES.txt 2020-01-06 14:47:05.000000000 +0000 @@ -0,0 +1,114 @@ +AUTHORS +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +django_tastypie.egg-info/PKG-INFO +django_tastypie.egg-info/SOURCES.txt +django_tastypie.egg-info/dependency_links.txt +django_tastypie.egg-info/not-zip-safe +django_tastypie.egg-info/requires.txt +django_tastypie.egg-info/top_level.txt +docs/Makefile +docs/api.rst +docs/authentication.rst +docs/authorization.rst +docs/bundles.rst +docs/caching.rst +docs/compatibility_notes.rst +docs/conf.py +docs/content_types.rst +docs/contributing.rst +docs/cookbook.rst +docs/debugging.rst +docs/fields.rst +docs/geodjango.rst +docs/index.rst +docs/interacting.rst +docs/namespaces.rst +docs/non_orm_data_sources.rst +docs/paginator.rst +docs/python3.rst +docs/resources.rst +docs/serialization.rst +docs/settings.rst +docs/testing.rst +docs/throttling.rst +docs/toc.rst +docs/tools.rst +docs/tutorial.rst +docs/validation.rst +docs/who_uses.rst +docs/code/authentication.py +docs/code/manage.py +docs/code/myapp/__init__.py +docs/code/myapp/models.py +docs/code/myapp/urls.py +docs/code/myapp/api/__init__.py +docs/code/myapp/api/resources.py +docs/code/myproject/__init__.py +docs/code/myproject/settings.py +docs/code/myproject/urls.py +docs/code/myproject/wsgi.py +docs/release_notes/dev.rst +docs/release_notes/index.rst +docs/release_notes/v0.10.0.rst +docs/release_notes/v0.11.0.rst +docs/release_notes/v0.11.1.rst +docs/release_notes/v0.12.0.rst +docs/release_notes/v0.12.1.rst +docs/release_notes/v0.12.2.rst +docs/release_notes/v0.13.0.rst +docs/release_notes/v0.13.1.rst +docs/release_notes/v0.13.2.rst +docs/release_notes/v0.13.3.rst +docs/release_notes/v0.14.0.rst +docs/release_notes/v0.14.1.rst +docs/release_notes/v0.14.2.rst +docs/release_notes/v0.14.3.rst +docs/release_notes/v0.9.13.rst +docs/release_notes/v0.9.14.rst +docs/release_notes/v0.9.15.rst +docs/release_notes/v0.9.16.rst +tastypie/__init__.py +tastypie/admin.py +tastypie/api.py +tastypie/authentication.py +tastypie/authorization.py +tastypie/bundle.py +tastypie/cache.py +tastypie/compat.py +tastypie/constants.py +tastypie/exceptions.py +tastypie/fields.py +tastypie/http.py +tastypie/models.py +tastypie/paginator.py +tastypie/resources.py +tastypie/serializers.py +tastypie/test.py +tastypie/throttle.py +tastypie/validation.py +tastypie/contrib/__init__.py +tastypie/contrib/contenttypes/__init__.py +tastypie/contrib/contenttypes/fields.py +tastypie/contrib/contenttypes/resources.py +tastypie/contrib/gis/__init__.py +tastypie/contrib/gis/resources.py +tastypie/management/__init__.py +tastypie/management/commands/__init__.py +tastypie/management/commands/backfill_api_keys.py +tastypie/migrations/0001_initial.py +tastypie/migrations/0002_api_access_url_length.py +tastypie/migrations/__init__.py +tastypie/templates/tastypie/basic.html +tastypie/templates/tastypie/detail.html +tastypie/templates/tastypie/list.html +tastypie/utils/__init__.py +tastypie/utils/dict.py +tastypie/utils/formatting.py +tastypie/utils/mime.py +tastypie/utils/timezone.py +tastypie/utils/urls.py +tastypie/utils/validate_jsonp.py \ No newline at end of file diff -Nru django-tastypie-0.13.3/django_tastypie.egg-info/top_level.txt django-tastypie-0.14.3/django_tastypie.egg-info/top_level.txt --- django-tastypie-0.13.3/django_tastypie.egg-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/django_tastypie.egg-info/top_level.txt 2020-01-06 14:47:05.000000000 +0000 @@ -0,0 +1 @@ +tastypie diff -Nru django-tastypie-0.13.3/docs/authentication.rst django-tastypie-0.14.3/docs/authentication.rst --- django-tastypie-0.13.3/docs/authentication.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/authentication.rst 2017-06-16 19:06:40.000000000 +0000 @@ -114,7 +114,7 @@ .. _`this post`: http://www.nerdydork.com/basic-authentication-on-mod_wsgi.html .. _`abstract base class`: https://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes -.. _`the documentation for this setting`: http://django-tastypie.readthedocs.org/en/latest/settings.html#tastypie-abstract-apikey +.. _`the documentation for this setting`: https://django-tastypie.readthedocs.io/en/latest/settings.html#tastypie-abstract-apikey ``SessionAuthentication`` ~~~~~~~~~~~~~~~~~~~~~~~~~ diff -Nru django-tastypie-0.13.3/docs/authorization.rst django-tastypie-0.14.3/docs/authorization.rst --- django-tastypie-0.13.3/docs/authorization.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/authorization.rst 2017-06-16 19:06:40.000000000 +0000 @@ -58,10 +58,44 @@ ~~~~~~~~~~~~~~~~~~~~~~~ The most advanced form of authorization, this checks the permission a user -has granted to them (via ``django.contrib.auth.models.Permission``). In +has granted to them on the resource's model (via ``django.contrib.auth.models.Permission``). In conjunction with the admin, this is a very effective means of control. +The permissions required using ``DjangoAuthorization`` follow Django Admin's implementation and are as follows: ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| HTTP + URI | Method | User’s permissions required to grant access | Includes check for | ++=================================+==================+===============================================+======================+ +| ``POST /`` | create\_list | ``add`` | | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``POST /`` (\*) | create\_detail | ``add`` | | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``GET /`` | read\_list | ``change`` | | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``GET /`` | read\_detail | ``change`` | | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``PUT /`` | update\_list | ``change`` | ``read_list`` | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``PUT /`` | update\_detail | ``change`` | ``read_detail`` | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``DELETE /`` | delete\_list | ``delete`` | ``read_list`` | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ +| ``DELETE /`` | delete\_detail | ``delete`` | ``read_detail`` | ++---------------------------------+------------------+-----------------------------------------------+----------------------+ + +(*) The permission check for ``create_detail`` is implemented in ``DjangoAuthorization``, however ModelResource does not provide an implementation and raises HttpNotImplemented. + + +Notes: + +* The actual permission checked is `._` where app_label is derived from the resource's model (e.g. `myapp.change_foomodel`) +* `PUT` may revert to `POST` behavior and create new object(s) if the object(s) are not found. In this case the respective `create` permissions are required, instead of the usual `update` permissions. +* Requiring `change` for both read and update is such to keep consistent with Django Admin. To override this behavior and require a custom permission, override DjangoAuthorization as follows:: + + class CustomDjangoAuthorization(DjangoAuthorization): + READ_PERM_CODE = 'view` # matching respective Permission.codename + + The ``Authorization`` API ========================= diff -Nru django-tastypie-0.13.3/docs/code/myapp/models.py django-tastypie-0.14.3/docs/code/myapp/models.py --- django-tastypie-0.13.3/docs/code/myapp/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/code/myapp/models.py 2019-03-01 02:40:46.000000000 +0000 @@ -1,6 +1,6 @@ import mock -from django.contrib.auth.models import User # flake8: noqa +from django.contrib.auth.models import User # noqa Choice = mock.MagicMock() diff -Nru django-tastypie-0.13.3/docs/code/myproject/settings.py django-tastypie-0.14.3/docs/code/myproject/settings.py --- django-tastypie-0.13.3/docs/code/myproject/settings.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/code/myproject/settings.py 2017-07-03 14:17:15.000000000 +0000 @@ -2,10 +2,10 @@ Django settings for myproject project. For more information on this file, see -https://docs.djangoproject.com/en/1.7/topics/settings/ +https://docs.djangoproject.com/en/1.8/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.7/ref/settings/ +https://docs.djangoproject.com/en/1.8/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -14,7 +14,7 @@ # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '%ovvje%lh&k-%0v!@_c1gygt#aq-!o3*t$(hpee7@aj&35cr3a' @@ -40,7 +40,7 @@ 'tastypie', ) -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -56,7 +56,7 @@ # Database -# https://docs.djangoproject.com/en/1.7/ref/settings/#databases +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { 'default': { @@ -66,7 +66,7 @@ } # Internationalization -# https://docs.djangoproject.com/en/1.7/topics/i18n/ +# https://docs.djangoproject.com/en/1.8/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -80,6 +80,6 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.7/howto/static-files/ +# https://docs.djangoproject.com/en/1.8/howto/static-files/ STATIC_URL = '/static/' diff -Nru django-tastypie-0.13.3/docs/code/myproject/wsgi.py django-tastypie-0.14.3/docs/code/myproject/wsgi.py --- django-tastypie-0.13.3/docs/code/myproject/wsgi.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/code/myproject/wsgi.py 2019-03-01 02:40:46.000000000 +0000 @@ -4,11 +4,11 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") -from django.core.wsgi import get_wsgi_application # flake8: noqa +from django.core.wsgi import get_wsgi_application # noqa application = get_wsgi_application() diff -Nru django-tastypie-0.13.3/docs/conf.py django-tastypie-0.14.3/docs/conf.py --- django-tastypie-0.13.3/docs/conf.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/conf.py 2019-03-01 02:40:46.000000000 +0000 @@ -23,7 +23,7 @@ sys.path.append('..') -from tastypie import __short_version__, __version__ # flake8: noqa +from tastypie import __short_version__, __version__ # noqa docs_path = os.path.dirname(__file__) doctest_path = [os.path.join(docs_path, 'code'), os.path.join(docs_path, '..')] diff -Nru django-tastypie-0.13.3/docs/content_types.rst django-tastypie-0.14.3/docs/content_types.rst --- django-tastypie-0.13.3/docs/content_types.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/content_types.rst 2017-07-03 14:17:15.000000000 +0000 @@ -23,7 +23,7 @@ class TaggedItem(models.Model): tag = models.SlugField() - content_type = models.ForeignKey(ContentType) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') diff -Nru django-tastypie-0.13.3/docs/cookbook.rst django-tastypie-0.14.3/docs/cookbook.rst --- django-tastypie-0.13.3/docs/cookbook.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/cookbook.rst 2019-12-23 21:22:21.000000000 +0000 @@ -121,7 +121,7 @@ .. testcode:: # views.py - from django.shortcuts import render_to_response + from django.shortcuts import render from myapp.api.resources import UserResource @@ -135,7 +135,7 @@ user_bundle = res.build_bundle(request=request, obj=user) user_json = res.serialize(None, res.full_dehydrate(user_bundle), "application/json") - return render_to_response("myapp/user_detail.html", { + return render(request, "myapp/user_detail.html", { # Other things here. "user_json": user_json, }) @@ -179,7 +179,7 @@ .. testcode:: # views.py - from django.shortcuts import render_to_response + from django.shortcuts import render from myapp.api.resources import UserResource @@ -209,7 +209,7 @@ list_json = res.serialize(None, bundles, "application/json") - return render_to_response('myapp/user_list.html', { + return render(request, 'myapp/user_list.html', { # Other things here. "list_json": list_json, }) @@ -376,26 +376,16 @@ # Do the query. sqs = SearchQuerySet().models(Note).load_all().auto_query(request.GET.get('q', '')) - paginator = Paginator(sqs, 20) - - try: - page = paginator.page(int(request.GET.get('page', 1))) - except InvalidPage: - raise Http404("Sorry, no results on that page.") - - objects = [] - - for result in page.object_list: - bundle = self.build_bundle(obj=result.object, request=request) - bundle = self.full_dehydrate(bundle) - objects.append(bundle) - - object_list = { - 'objects': objects, - } - - self.log_throttled_access(request) - return self.create_response(request, object_list) + paginator = self._meta.paginator_class(request.GET, sqs, + resource_uri=self.get_resource_uri(), limit=self._meta.limit, + max_limit=self._meta.max_limit, collection_name=self._meta.collection_name) + + to_be_serialized = paginator.page() + + bundles = [self.build_bundle(obj=result.object, request=request) for result in to_be_serialized['objects']] + to_be_serialized['objects'] = [self.full_dehydrate(bundle) for bundle in bundles] + to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized) + return self.create_response(request, to_be_serialized) .. _Haystack: http://haystacksearch.org/ @@ -407,8 +397,8 @@ and every user will be working only with objects associated with them. Let's see how to implement it for two basic operations: listing and creation of an object. -For listing we want to list only objects for which 'user' field matches -'request.user'. This could be done by applying a filter in the +For listing we want to list only objects for which ``user`` field matches +``request.user``. This could be done by applying a filter in the ``authorized_read_list`` method of your resource. For creating we'd have to wrap ``obj_create`` method of ``ModelResource``. Then the @@ -476,9 +466,9 @@ new_key = re.sub(r"[a-z]_[a-z]", underscoreToCamel, key) new_dict[new_key] = camelize(value) return new_dict - if isinstance(data, (list, tuple)): - for i in range(len(data)): - data[i] = camelize(data[i]) + if isinstance(data, list): + for i, v in enumerate(data): + data[i] = camelize(v) return data return data @@ -500,9 +490,9 @@ new_key = re.sub(r"[a-z][A-Z]", camelToUnderscore, key) new_dict[new_key] = underscorize(value) return new_dict - if isinstance(data, (list, tuple)): - for i in range(len(data)): - data[i] = underscorize(data[i]) + if isinstance(data, list): + for i, v in enumerate(data): + data[i] = underscorize(v) return data return data diff -Nru django-tastypie-0.13.3/docs/debugging.rst django-tastypie-0.14.3/docs/debugging.rst --- django-tastypie-0.13.3/docs/debugging.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/debugging.rst 2018-08-06 13:52:47.000000000 +0000 @@ -29,6 +29,14 @@ ``Accept`` header. +Querying using Tastypie's methods isn't working/returning multiple objects +========================================================================== + +When calling ``obj_get`` (or another method that uses it, such as +``dispatch_detail``), make sure the fields you're querying with are either +``Meta.detail_uri_name`` or a field which appears in ``Meta.filtering`` + + "What's the format for a POST or PUT?" ====================================== diff -Nru django-tastypie-0.13.3/docs/fields.rst django-tastypie-0.14.3/docs/fields.rst --- django-tastypie-0.13.3/docs/fields.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/fields.rst 2019-04-08 13:57:52.000000000 +0000 @@ -51,7 +51,7 @@ .. attribute:: ApiField.attribute A string naming an instance attribute of the object wrapped by the Resource. The -attribute will be accessed during the ``dehydrate`` or or written during the ``hydrate``. +attribute will be accessed during the ``dehydrate`` or written during the ``hydrate``. Defaults to ``None``, meaning data will be manually accessed. @@ -235,7 +235,7 @@ list of resources. The value is one of ``True``, ``False`` or a callable that accepts a bundle and returns ``True`` or ``False``. If ``False``, the related ``Resource`` will appear as a URL to the endpoint of that resource if accessing a list of resources. If ``True`` and ``full`` -is also ``True``, the result of thesub-resource's ``dehydrate`` will be included in +is also ``True``, the result of the sub-resource's ``dehydrate`` will be included in full. Default is ``True`` ``full_detail`` @@ -247,7 +247,7 @@ single resource. The value is one of ``True``, ``False`` or a callable that accepts a bundle and returns ``True`` or ``False``. If ``False``, the related ``Resource`` will appear as a URL to the endpoint of that resource if accessing a specific resources. If ``True`` and ``full`` -is also ``True``, the result of thesub-resource's ``dehydrate`` will be included +is also ``True``, the result of the sub-resource's ``dehydrate`` will be included in full. Default is ``True`` ``related_name`` @@ -312,6 +312,8 @@ subjects = fields.ToManyField(SubjectResource, attribute=lambda bundle: Subject.objects.filter(notes=bundle.obj, name__startswith='Personal')) +The callable should either return an iterable of objects or ``None``. + Note that the ``hydrate`` portions of this field are quite different than any other field. ``hydrate_m2m`` actually handles the data and relations. This is due to the way Django implements M2M relationships. diff -Nru django-tastypie-0.13.3/docs/geodjango.rst django-tastypie-0.14.3/docs/geodjango.rst --- django-tastypie-0.13.3/docs/geodjango.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/geodjango.rst 2017-09-11 14:01:37.000000000 +0000 @@ -27,6 +27,7 @@ we use ``tastypie.contrib.gis.resources.ModelResource``:: from tastypie.contrib.gis.resources import ModelResource + from tastypie.resources import ALL class GeoNoteResource(ModelResource): class Meta: diff -Nru django-tastypie-0.13.3/docs/index.rst django-tastypie-0.14.3/docs/index.rst --- django-tastypie-0.13.3/docs/index.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/index.rst 2019-12-16 14:58:39.000000000 +0000 @@ -83,8 +83,8 @@ Core ---- -* Python 2.7+ or Python 3.4+ -* Django 1.7 through Django 1.9 +* Python 2.7+ or Python 3.4+ (Whatever is supported by your version of Django) +* Django 1.11, 2.2 (LTS releases) or Django 3.0 (latest release) * dateutil (http://labix.org/python-dateutil) >= 2.1 Format Support @@ -92,7 +92,7 @@ * XML: lxml 3 (http://lxml.de/) and defusedxml (https://pypi.python.org/pypi/defusedxml) * YAML: pyyaml (http://pyyaml.org/) -* binary plist: biplist (http://explorapp.com/biplist/) +* binary plist: biplist (https://bitbucket.org/wooster/biplist) Optional -------- @@ -119,7 +119,7 @@ Reference Material ================== -* https://django-tastypie.readthedocs.org/en/latest/ +* https://django-tastypie.readthedocs.io/en/latest/ * https://github.com/django-tastypie/django-tastypie/tree/master/tests/basic shows basic usage of tastypie * http://en.wikipedia.org/wiki/REST diff -Nru django-tastypie-0.13.3/docs/interacting.rst django-tastypie-0.14.3/docs/interacting.rst --- django-tastypie-0.13.3/docs/interacting.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/interacting.rst 2017-06-16 19:06:40.000000000 +0000 @@ -628,11 +628,11 @@ Content-Length: 0 Content-Type: text/html; charset=utf-8 -If we request that resource, we get a 410 to show it's no longer there:: +If we request that resource, we get a 404 to show it's no longer there:: curl --dump-header - http://localhost:8000/api/v1/entry/4/ - HTTP/1.0 410 GONE + HTTP/1.0 404 GONE Date: Fri, 20 May 2011 07:29:02 GMT Server: WSGIServer/0.1 Python/2.7 Content-Type: text/html; charset=utf-8 @@ -640,7 +640,7 @@ Additionally, if we try to run the ``DELETE`` again (using the same original command), we get the "Gone" response again:: - HTTP/1.0 410 GONE + HTTP/1.0 404 GONE Date: Fri, 20 May 2011 07:30:00 GMT Server: WSGIServer/0.1 Python/2.7 Content-Type: text/html; charset=utf-8 diff -Nru django-tastypie-0.13.3/docs/non_orm_data_sources.rst django-tastypie-0.14.3/docs/non_orm_data_sources.rst --- django-tastypie-0.13.3/docs/non_orm_data_sources.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/non_orm_data_sources.rst 2017-06-16 19:06:40.000000000 +0000 @@ -156,7 +156,7 @@ pass This represents a full, working, Riak-powered API endpoint. All REST-style -actions (GET/POST/PUT/DELETE) all work correctly. The only shortcut taken in +actions (GET/POST/PUT/DELETE) work correctly. The only shortcut taken in this example was skipping filter-abilty, as adding in the MapReduce bits would have decreased readability. diff -Nru django-tastypie-0.13.3/docs/paginator.rst django-tastypie-0.14.3/docs/paginator.rst --- django-tastypie-0.13.3/docs/paginator.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/paginator.rst 2017-06-16 19:06:40.000000000 +0000 @@ -112,7 +112,7 @@ # to return a url. Setting it to an unreasonably large value, so that # the parent method will always return the url. count = 2 ** 64 - return super(NoTotalCountPaginator, self).get_next(limit, offset, count) + return super(EstimatedCountPaginator, self).get_next(limit, offset, count) def get_count(self): return None @@ -160,6 +160,6 @@ return rows def page(self): - data = super(NoTotalCountPaginator, self).page() + data = super(EstimatedCountPaginator, self).page() data['meta']['estimated_count'] = self.get_estimated_count() return data diff -Nru django-tastypie-0.13.3/docs/release_notes/dev.rst django-tastypie-0.14.3/docs/release_notes/dev.rst --- django-tastypie-0.13.3/docs/release_notes/dev.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/dev.rst 2019-12-16 15:08:11.000000000 +0000 @@ -4,7 +4,11 @@ The current in-progress version. Put your notes here so they can be easily copied to the release notes for the next release. +Major changes +------------- + + Bugfixes -------- -* list of changes +* Example Bugfix (Closes #PR_Number) diff -Nru django-tastypie-0.13.3/docs/release_notes/index.rst django-tastypie-0.14.3/docs/release_notes/index.rst --- django-tastypie-0.13.3/docs/release_notes/index.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/index.rst 2019-12-16 15:07:56.000000000 +0000 @@ -5,6 +5,10 @@ :maxdepth: 1 dev + v0.14.3 + v0.14.2 + v0.14.1 + v0.14.0 v0.13.3 v0.13.2 v0.13.1 diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.11.0.rst django-tastypie-0.14.3/docs/release_notes/v0.11.0.rst --- django-tastypie-0.13.3/docs/release_notes/v0.11.0.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.11.0.rst 2017-06-16 19:06:40.000000000 +0000 @@ -11,7 +11,7 @@ pip install wheel pip install --use-wheel django-tastypie -.. _`Python wheel`: http://wheel.readthedocs.org/ +.. _`Python wheel`: https://wheel.readthedocs.io/ Bugfixes diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.12.2.rst django-tastypie-0.14.3/docs/release_notes/v0.12.2.rst --- django-tastypie-0.13.3/docs/release_notes/v0.12.2.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.12.2.rst 2017-06-16 19:06:40.000000000 +0000 @@ -1,4 +1,4 @@ -v0.12.1 +v0.12.2 ======= :date: 2015-07-16 diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.13.3.rst django-tastypie-0.14.3/docs/release_notes/v0.13.3.rst --- django-tastypie-0.13.3/docs/release_notes/v0.13.3.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.13.3.rst 2017-07-03 14:30:16.000000000 +0000 @@ -3,6 +3,8 @@ :date: 2016-02-17 +This is the final release of Tastypie that is compatible with Django 1.9. + Bugfixes -------- diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.14.0.rst django-tastypie-0.14.3/docs/release_notes/v0.14.0.rst --- django-tastypie-0.13.3/docs/release_notes/v0.14.0.rst 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.14.0.rst 2017-07-10 13:29:16.000000000 +0000 @@ -0,0 +1,29 @@ +v0.14.0 +======= + +:date: 2017-07-03 + +Dropped support for all non-LTS versions of Django. Specifically supports Django 1.8 and 1.11. + +Deprecations +------------ + +* Removed `ResourceTestCase` and added `ResourceTestCaseMixin`. + +Bugfixes +-------- + +* Change OAuthAuthentication to use storage method to get user. (Closes #657) +* Fixed UnicodeDecodeError in _handle_500(). (Fixes #1190) +* Fix get_via_uri not working for alphabetic ids that contain the resource name (Fixes #1239, Closes #1240) +* Don't enable unsupported formats by default. (Fixes #1451) +* Gave ApiKey a __str__ implementation that works in Python 2 and 3. (Fixes #1459, Closes #1460) +* Improved admin UI for API Keys (Closes #1262) +* Avoid double query on `the_m2ms` in `ToManyField.dehydrate`. (Closes #433) +* Allow `ModelResource.Meta.fields = []` to disable introspection. `ModelResource.Meta.fields = None` or omitting `ModelResource.Meta.fields` allows introspection as usual. (Fixes #793) +* Added `Resource.get_response_class_for_exception` hook. (Closes #1154) +* Added UnsupportedSerializationFormat and UnsupportedDeserializationFormat exceptions, which are caught and result in HttpNotAcceptable (406 status) and HttpUnsupportedMediaType (415 status) responses, respectively. Previously these same types of errors woud have appeared as 400 BadRequest errors. +* Fix for datetime parsing error. (#1478) +* Gets rid of RemovedInDjango20Warning warning in Django 1.9 (Closes #1507) + + diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.14.1.rst django-tastypie-0.14.3/docs/release_notes/v0.14.1.rst --- django-tastypie-0.13.3/docs/release_notes/v0.14.1.rst 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.14.1.rst 2018-09-04 14:48:45.000000000 +0000 @@ -0,0 +1,17 @@ +v0.14.1 +======= + +:date: 2018-04-09 + +Added support for Django 2.0; minor bugs and regressions fixed. +Adds abstract base classes for Resources - see documentation. +Adds optimization for getting multiple objects by id in a single query. + +Bugfixes +-------- + +* Add explicit error when ModelResource lacks object_class and queryset +* Update urls to slumber and biplist +* Alter length of URL field in ApiAccess model (to open-ended TextField) +* Fix Advanced Filtering example in documentation (with unit test) +* Fix regression in ToManyField.dehydrate with a null value. (#1544) diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.14.2.rst django-tastypie-0.14.3/docs/release_notes/v0.14.2.rst --- django-tastypie-0.13.3/docs/release_notes/v0.14.2.rst 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.14.2.rst 2018-09-04 14:48:45.000000000 +0000 @@ -0,0 +1,7 @@ +v0.14.2 +======= + +:date: 2018-08-20 + +Added support for Django 2.1; minor bugs and regressions fixed. +Drops support for Django 1.8 LTS. diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.14.3.rst django-tastypie-0.14.3/docs/release_notes/v0.14.3.rst --- django-tastypie-0.13.3/docs/release_notes/v0.14.3.rst 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.14.3.rst 2020-01-06 14:42:24.000000000 +0000 @@ -0,0 +1,9 @@ +v0.14.3 +======= + +:date: 2019-12-16 + +Added support for Django 3.0; minor bugs and regressions fixed. +Drops explicit support for Django 2.1 (non-LTS). + +This will be the last version to explicitly support Python 2.0. diff -Nru django-tastypie-0.13.3/docs/release_notes/v0.9.13.rst django-tastypie-0.14.3/docs/release_notes/v0.9.13.rst --- django-tastypie-0.13.3/docs/release_notes/v0.9.13.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/release_notes/v0.9.13.rst 2017-06-16 19:06:40.000000000 +0000 @@ -20,12 +20,12 @@ restrict the set of supported formats (closes `#833 `_): - http://django-tastypie.readthedocs.org/en/v0.9.14/settings.html#tastypie-default-formats + https://django-tastypie.readthedocs.io/en/v0.9.14/settings.html#tastypie-default-formats * Content negotiation will return an error for malformed accept headers (closes `#832 `_) * The Api class itself now allows a custom serializer (closes `#817 `_) * The serialization documentation has been upgraded with security advice: - http://django-tastypie.readthedocs.org/en/v0.9.14/serialization.html#serialization-security + https://django-tastypie.readthedocs.io/en/v0.9.14/serialization.html#serialization-security Upgrade notes: diff -Nru django-tastypie-0.13.3/docs/resources.rst django-tastypie-0.14.3/docs/resources.rst --- django-tastypie-0.13.3/docs/resources.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/resources.rst 2019-12-09 15:31:53.000000000 +0000 @@ -160,7 +160,7 @@ Accessing The Current Request ============================= -Being able to change behavior based on the current request is a very commmon +Being able to change behavior based on the current request is a very common need. Virtually anywhere within ``Resource/ModelResource``, if a ``bundle`` is available, you can access it using ``bundle.request``. This is useful for altering querysets, ensuring headers are present, etc. @@ -232,7 +232,7 @@ The ``FOO`` here is not literal. Instead, it is a placeholder that should be replaced with the fieldname in question. -Defining these methods is especially common when denormalizing related data, +Defining these methods is especially common when deserializing related data, providing statistics or filling in unrelated data. A simple example:: @@ -316,7 +316,7 @@ The Hydrate Cycle ------------------- -Tastypie uses a "hydrate" cycle to take serializated data from the client +Tastypie uses a "hydrate" cycle to take serialized data from the client and turn it into something the data model can use. This is the reverse process from the ``dehydrate`` cycle. In fact, by default, Tastypie's serialized data should be "round-trip-able", meaning the data that comes out should be able to @@ -592,6 +592,9 @@ Keys should be the fieldnames as strings while values should be a list of accepted filter types. + This also restricts what fields can be filtered on when manually + calling ``obj_get`` and ``obj_get_list``. + ``ordering`` ------------ @@ -625,11 +628,23 @@ the ``Meta`` class is instantiated). This especially affects things that are date/time related. Please see the :doc:`cookbook` for a way around this. +``abstract`` +------------ + + In concrete ``Resource`` and ``ModelResource`` instances, either + ``object_class`` or ``queryset`` is required. + If you wish to build an abstract base ``Resource`` class, you can bypass + this requirement by setting ``abstract`` to ``True``. + ``fields`` ---------- Controls what introspected fields the ``Resource`` should include. - A whitelist of fields. Default is ``[]``. + A whitelist of fields. Default is ``None``. + + The default value of ``None`` means that all Django fields will be + introspected. In order to specify that no fields should be introspected, + use ``[]`` ``excludes`` ------------ @@ -719,11 +734,11 @@ from haystack.query import SearchQuerySet class MyResource(Resource): - def build_filters(self, filters=None): + def build_filters(self, filters=None, **kwargs): if filters is None: filters = {} - orm_filters = super(MyResource, self).build_filters(filters) + orm_filters = super(MyResource, self).build_filters(filters, **kwargs) if "q" in filters: sqs = SearchQuerySet().auto_query(filters['q']) @@ -768,6 +783,14 @@ there is special handling to either present a message back to the user or return the response traveling with the exception. +``get_response_class_for_exception`` +------------------------------------ + +.. method:: Resource.get_response_class_for_exception(self, request, exception) + +Can be overridden to customize response classes used for uncaught exceptions. +Should always return a subclass of``django.http.HttpResponse``. + ``base_urls`` ------------- @@ -847,10 +870,21 @@ .. method:: Resource.alter_list_data_to_serialize(self, request, data) -A hook to alter list data just before it gets serialized & sent to the user. +A hook to alter list data just before it gets serialized & sent to the user. Useful for restructuring/renaming aspects of the what's going to be -sent. +sent. Occurs after any dehydration has by applied. + +As such this is a useful place to apply modifications which affect many +list elements. + +Example:: + + def alter_list_data_to_serialize(self, request, data): + bar = some_expensive_call() + for obj in data['objects']: + obj.foo = bar + return data Should accommodate for a list of objects, generally also including meta data. @@ -1099,7 +1133,7 @@ be called once for each object requested. - ``False`` indicates a response showing the details for an item -This method is responsible for invoking the the :meth:`dehydrate` method of +This method is responsible for invoking the :meth:`dehydrate` method of all the fields in the resource. Additionally, it calls :meth:`Resource.dehydrate`. diff -Nru django-tastypie-0.13.3/docs/serialization.rst django-tastypie-0.14.3/docs/serialization.rst --- django-tastypie-0.13.3/docs/serialization.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/serialization.rst 2018-04-09 15:02:13.000000000 +0000 @@ -41,7 +41,7 @@ * jsonp (Disabled by default) * xml * yaml -* plist (see http://explorapp.com/biplist/) +* plist (see https://bitbucket.org/wooster/biplist) Not everyone wants to install or support all the serialization options. If you would like to customize the list of supported formats for your entire site diff -Nru django-tastypie-0.13.3/docs/tools.rst django-tastypie-0.14.3/docs/tools.rst --- django-tastypie-0.13.3/docs/tools.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/tools.rst 2017-07-31 14:05:14.000000000 +0000 @@ -46,7 +46,8 @@ Slumber ------- -http://slumber.in/ +https://pypi.python.org/pypi/slumber/ +https://github.com/samgiles/slumber Slumber is a small Python library that makes it easy to access & work with APIs. It works for many others, but works especially well with Tastypie. @@ -69,7 +70,7 @@ drest ----- -http://drest.rtfd.org/ +https://drest.readthedocs.io/ drest is another small Python library. It focuses on extensibility & can also work with many different API, with an emphasis on Tastypie. diff -Nru django-tastypie-0.13.3/docs/tutorial.rst django-tastypie-0.14.3/docs/tutorial.rst --- django-tastypie-0.13.3/docs/tutorial.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/tutorial.rst 2017-07-03 14:17:15.000000000 +0000 @@ -32,7 +32,7 @@ class Entry(models.Model): - user = models.ForeignKey(User) + user = models.ForeignKey(User, on_delete=models.CASCADE) pub_date = models.DateTimeField(default=now) title = models.CharField(max_length=200) slug = models.SlugField(null=True, blank=True) @@ -60,7 +60,7 @@ 1. Download the dependencies: * Python 2.7+ or Python 3.4+ - * Django 1.7+ + * Django 1.8+ * ``python-mimeparse`` 0.1.4+ (http://pypi.python.org/pypi/python-mimeparse) * ``dateutil`` (http://labix.org/python-dateutil) * **OPTIONAL** - ``lxml`` (http://lxml.de/) and ``defusedxml`` (https://pypi.python.org/pypi/defusedxml) if using the XML serializer @@ -227,6 +227,11 @@ class EntryResource(ModelResource): + # Maps `Entry.user` to a Tastypie `ForeignKey` field named `user`, + # which gets serialized using `UserResource`. The first appearance of + # 'user' on the next line of code is the Tastypie field name, the 2nd + # appearance tells the `ForeignKey` it maps to the `user` attribute of + # `Entry`. Field names and model attributes don't have to be the same. user = fields.ForeignKey(UserResource, 'user') class Meta: diff -Nru django-tastypie-0.13.3/docs/who_uses.rst django-tastypie-0.14.3/docs/who_uses.rst --- django-tastypie-0.13.3/docs/who_uses.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/docs/who_uses.rst 2019-04-08 13:57:52.000000000 +0000 @@ -3,17 +3,20 @@ Sites Using Tastypie ==================== -The following sites are a partial list of people using Tastypie. I'm always -interested in adding more sites, so please find me (``daniellindsley``) via -IRC or start a mailing list thread. +The following sites are a partial list of sites using Tastypie. We're always +interested in adding more sites, so please open a GitHub Issue or Pull Request +for this page and we'll add you to the list. +Teachoo +--------- + +`Teachoo `_ uses Tastypie for its API LJWorld Marketplace ------------------- * http://www2.ljworld.com/marketplace/api/v1/?format=json - Forkinit -------- @@ -39,7 +42,7 @@ An e-book search site that lets you fetch pricing information. * http://luzme.com/ -* http://luzme.readthedocs.org/en/latest/ +* https://luzme.readthedocs.io/en/latest/ Politifact @@ -57,7 +60,7 @@ geographic communities. It's using Tastypie to provide an geospatially-aware REST API. -* http://localwiki.readthedocs.org/en/latest/api.html +* https://localwiki.readthedocs.io/en/latest/api.html * http://localwiki.org/blog/2012/aug/31/localwiki-api-released/ diff -Nru django-tastypie-0.13.3/.gitignore django-tastypie-0.14.3/.gitignore --- django-tastypie-0.13.3/.gitignore 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/.gitignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -*.pyc -.DS_Store -_build -.*.sw[po] -*.egg-info -dist -build -MANIFEST -tests/tastypie.db -.buildinfo -.tox -env -env3 diff -Nru django-tastypie-0.13.3/PKG-INFO django-tastypie-0.14.3/PKG-INFO --- django-tastypie-0.13.3/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/PKG-INFO 2020-01-06 14:47:05.000000000 +0000 @@ -0,0 +1,170 @@ +Metadata-Version: 1.1 +Name: django-tastypie +Version: 0.14.3 +Summary: A flexible & capable API layer for Django. +Home-page: https://github.com/django-tastypie/django-tastypie +Author: Daniel Lindsley +Author-email: daniel@toastdriven.com +License: UNKNOWN +Description: =============== + django-tastypie + =============== + + .. image:: https://readthedocs.org/projects/django-tastypie/badge/ + :target: https://django-tastypie.readthedocs.io/ + :alt: Docs + + .. image:: https://travis-ci.org/django-tastypie/django-tastypie.svg?branch=master + :target: https://travis-ci.org/django-tastypie/django-tastypie + :alt: CI + + .. image:: https://coveralls.io/repos/django-tastypie/django-tastypie/badge.svg?service=github + :target: https://coveralls.io/github/django-tastypie/django-tastypie + :alt: Code Coverage + + .. image:: https://img.shields.io/pypi/v/django-tastypie.svg + :target: https://pypi.python.org/pypi/django-tastypie + :alt: Version + + .. image:: https://pypi-badges.global.ssl.fastly.net/svg?package=django-tastypie&timeframe=monthly + :target: https://pypi.python.org/pypi/django-tastypie + :alt: Downloads + + Creating delicious APIs for Django apps since 2010. + + Currently in beta but being used actively in production on several + sites. + + + Requirements + ============ + + Core + ---- + + * Python 2.7+ or Python 3.4+ (Whatever is supported by your version of Django) + * Django 1.11, 2.2 (LTS releases) or Django 3.0 (latest release) + * dateutil (http://labix.org/python-dateutil) >= 2.1 + + Format Support + -------------- + + * XML: lxml 3 (http://lxml.de/) and defusedxml (https://pypi.python.org/pypi/defusedxml) + * YAML: pyyaml (http://pyyaml.org/) + * binary plist: biplist (https://bitbucket.org/wooster/biplist) + + Optional + -------- + + * HTTP Digest authentication: python3-digest (https://bitbucket.org/akoha/python-digest/) + + + What's It Look Like? + ==================== + + A basic example looks like: + + .. code:: python + + # myapp/api.py + # ============ + from tastypie.resources import ModelResource + from myapp.models import Entry + + + class EntryResource(ModelResource): + class Meta: + queryset = Entry.objects.all() + + + # urls.py + # ======= + from django.conf.urls import url, include + from tastypie.api import Api + from myapp.api import EntryResource + + v1_api = Api(api_name='v1') + v1_api.register(EntryResource()) + + urlpatterns = [ + # The normal jazz here then... + url(r'^api/', include(v1_api.urls)), + ] + + That gets you a fully working, read-write API for the ``Entry`` model that + supports all CRUD operations in a RESTful way. JSON/XML/YAML support is already + there, and it's easy to add related data/authentication/caching. + + You can find more in the documentation at + https://django-tastypie.readthedocs.io/. + + + Why Tastypie? + ============= + + There are other API frameworks out there for Django. You need to + assess the options available and decide for yourself. That said, here are some + common reasons for tastypie. + + * You need an API that is RESTful and uses HTTP well. + * You want to support deep relations. + * You DON'T want to have to write your own serializer to make the output right. + * You want an API framework that has little magic, very flexible and maps well to + the problem domain. + * You want/need XML serialization that is treated equally to JSON (and YAML is + there too). + + + Reference Material + ================== + + * https://django-tastypie.readthedocs.io/en/latest/ + * https://github.com/django-tastypie/django-tastypie/tree/master/tests/basic shows + basic usage of tastypie + * http://en.wikipedia.org/wiki/REST + * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + * http://www.ietf.org/rfc/rfc2616.txt + * http://jacobian.org/writing/rest-worst-practices/ + + + Getting Help + ============ + + There are two primary ways of getting help. + + 1. Go to `StackOverflow`_ and post a question with the ``tastypie`` tag. + 2. We have an IRC channel (`#tastypie on irc.freenode.net`_) to get help, + bounce an idea by us, or generally shoot the breeze. + + .. _`StackOverflow`: https://stackoverflow.com/questions/tagged/tastypie + .. _#tastypie on irc.freenode.net: irc://irc.freenode.net/tastypie + + + Security + ======== + + Tastypie is committed to providing a flexible and secure API, and was designed + with many security features and options in mind. Due to the complex nature of + APIs and the constant discovery of new attack vectors and vulnerabilities, + no software is immune to security holes. We rely on our community to report + and help us investigate security issues. + + If you come across a security hole **please do not open a Github issue**. + Instead, **drop us an email** at ``tastypie-security@googlegroups.com`` + + We'll then work together to investigate and resolve the problem so we can + announce a solution along with the vulnerability. + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Utilities +Requires: python_mimeparse(>=0.1.4, !=1.5) +Requires: dateutil(>=1.5, !=2.0) diff -Nru django-tastypie-0.13.3/README.rst django-tastypie-0.14.3/README.rst --- django-tastypie-0.13.3/README.rst 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/README.rst 2019-12-16 14:59:08.000000000 +0000 @@ -3,10 +3,10 @@ =============== .. image:: https://readthedocs.org/projects/django-tastypie/badge/ - :target: https://django-tastypie.readthedocs.org/ + :target: https://django-tastypie.readthedocs.io/ :alt: Docs -.. image:: https://travis-ci.org/django-tastypie/django-tastypie.svg +.. image:: https://travis-ci.org/django-tastypie/django-tastypie.svg?branch=master :target: https://travis-ci.org/django-tastypie/django-tastypie :alt: CI @@ -18,13 +18,13 @@ :target: https://pypi.python.org/pypi/django-tastypie :alt: Version -.. image:: https://img.shields.io/pypi/dm/django-tastypie.svg +.. image:: https://pypi-badges.global.ssl.fastly.net/svg?package=django-tastypie&timeframe=monthly :target: https://pypi.python.org/pypi/django-tastypie :alt: Downloads Creating delicious APIs for Django apps since 2010. -Currently in beta (v0.13.3) but being used actively in production on several +Currently in beta but being used actively in production on several sites. @@ -34,8 +34,8 @@ Core ---- -* Python 2.7+ or Python 3.4+ -* Django 1.7 through Django 1.9 +* Python 2.7+ or Python 3.4+ (Whatever is supported by your version of Django) +* Django 1.11, 2.2 (LTS releases) or Django 3.0 (latest release) * dateutil (http://labix.org/python-dateutil) >= 2.1 Format Support @@ -43,7 +43,7 @@ * XML: lxml 3 (http://lxml.de/) and defusedxml (https://pypi.python.org/pypi/defusedxml) * YAML: pyyaml (http://pyyaml.org/) -* binary plist: biplist (http://explorapp.com/biplist/) +* binary plist: biplist (https://bitbucket.org/wooster/biplist) Optional -------- @@ -88,7 +88,7 @@ there, and it's easy to add related data/authentication/caching. You can find more in the documentation at -http://django-tastypie.readthedocs.org/. +https://django-tastypie.readthedocs.io/. Why Tastypie? @@ -110,7 +110,7 @@ Reference Material ================== -* https://django-tastypie.readthedocs.org/en/latest/ +* https://django-tastypie.readthedocs.io/en/latest/ * https://github.com/django-tastypie/django-tastypie/tree/master/tests/basic shows basic usage of tastypie * http://en.wikipedia.org/wiki/REST diff -Nru django-tastypie-0.13.3/requirements.txt django-tastypie-0.14.3/requirements.txt --- django-tastypie-0.13.3/requirements.txt 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/requirements.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -python-dateutil>=2.1 -python-mimeparse>=0.1.4 diff -Nru django-tastypie-0.13.3/setup.cfg django-tastypie-0.14.3/setup.cfg --- django-tastypie-0.13.3/setup.cfg 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/setup.cfg 2020-01-06 14:47:05.000000000 +0000 @@ -1,2 +1,12 @@ [wheel] universal = 1 + +[flake8] +exclude = .*/,tastypie/migrations/0001_initial.py +ignore = E128,F405,E501,W503 +max-line-length = 119 + +[egg_info] +tag_build = +tag_date = 0 + diff -Nru django-tastypie-0.13.3/tastypie/admin.py django-tastypie-0.14.3/tastypie/admin.py --- django-tastypie-0.13.3/tastypie/admin.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/admin.py 2019-02-28 22:54:58.000000000 +0000 @@ -10,6 +10,11 @@ model = ApiKey extra = 0 + class ApiKeyAdmin(admin.ModelAdmin): + search_fields = ['key', 'user__username'] + raw_id_fields = ['user'] + list_display = ['id', 'user', 'key', 'created'] + ABSTRACT_APIKEY = getattr(settings, 'TASTYPIE_ABSTRACT_APIKEY', False) if ABSTRACT_APIKEY and not isinstance(ABSTRACT_APIKEY, bool): @@ -17,4 +22,4 @@ "or 'False'.") if not ABSTRACT_APIKEY: - admin.site.register(ApiKey) + admin.site.register(ApiKey, ApiKeyAdmin) diff -Nru django-tastypie-0.13.3/tastypie/api.py django-tastypie-0.14.3/tastypie/api.py --- django-tastypie-0.13.3/tastypie/api.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/api.py 2019-02-28 22:54:58.000000000 +0000 @@ -2,8 +2,8 @@ import warnings from django.conf.urls import url, include from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseBadRequest +from tastypie.compat import reverse from tastypie.exceptions import NotRegistered, BadRequest from tastypie.serializers import Serializer from tastypie.utils import is_valid_jsonp_callback_value, string_to_python, trailing_slash diff -Nru django-tastypie-0.13.3/tastypie/authentication.py django-tastypie-0.14.3/tastypie/authentication.py --- django-tastypie-0.13.3/tastypie/authentication.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/authentication.py 2019-12-09 15:31:56.000000000 +0000 @@ -4,15 +4,19 @@ import hmac import time import uuid +import warnings from django.conf import settings from django.contrib.auth import authenticate from django.core.exceptions import ImproperlyConfigured from django.middleware.csrf import _sanitize_token, constant_time_compare -from django.utils.six.moves.urllib.parse import urlparse from django.utils.translation import ugettext as _ -from tastypie.compat import get_user_model, get_username_field +from six.moves.urllib.parse import urlparse + +from tastypie.compat import ( + get_user_model, get_username_field, unsalt_token, is_authenticated +) from tastypie.http import HttpUnauthorized try: @@ -73,7 +77,7 @@ try: auth_type, data = authorization.split(' ', 1) - except: + except ValueError: raise ValueError('Authorization header must have a space separating auth_type and data.') if auth_type.lower() != self.auth_type: @@ -169,11 +173,14 @@ password=password ) else: + if not self.require_active and 'django.contrib.auth.backends.ModelBackend' in settings.AUTHENTICATION_BACKENDS: + warnings.warn("Authenticating inactive users via ModelUserBackend not supported for Django >= 1.10") user = authenticate(username=username, password=password) if user is None: return self._unauthorized() + # Kept for backwards-compatibility with Django < 1.10 if not self.check_active(user): return False @@ -297,11 +304,12 @@ # wrong. # We also can't risk accessing ``request.POST``, which will break with # the serialized bodies. + if request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): - return request.user.is_authenticated() + return is_authenticated(request.user) if getattr(request, '_dont_enforce_csrf_checks', False): - return request.user.is_authenticated() + return is_authenticated(request.user) csrf_token = _sanitize_token(request.COOKIES.get(settings.CSRF_COOKIE_NAME, '')) @@ -317,11 +325,13 @@ return False request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') + request_csrf_token = _sanitize_token(request_csrf_token) - if not constant_time_compare(request_csrf_token, csrf_token): + if not constant_time_compare(unsalt_token(request_csrf_token), + unsalt_token(csrf_token)): return False - return request.user.is_authenticated() + return is_authenticated(request.user) def get_identifier(self, request): """ @@ -488,10 +498,11 @@ return oauth_provider.utils.send_oauth_error(e) if consumer and token: - if not self.check_active(token.user): + user = store.get_user_for_access_token(request, oauth_request, token) + if not self.check_active(user): return False - request.user = token.user + request.user = user return True return oauth_provider.utils.send_oauth_error(oauth2.Error(_('You are not allowed to access this resource.'))) @@ -518,7 +529,9 @@ according to OAuth spec) or fall back to ``GET/POST``. """ auth_params = request.META.get("HTTP_AUTHORIZATION", []) - return self.is_in(auth_params) or self.is_in(request.REQUEST) + return (self.is_in(auth_params) + or self.is_in(request.POST) + or self.is_in(request.GET)) def validate_token(self, request, consumer, token): oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) diff -Nru django-tastypie-0.13.3/tastypie/bundle.py django-tastypie-0.14.3/tastypie/bundle.py --- django-tastypie-0.13.3/tastypie/bundle.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/bundle.py 2019-12-09 15:31:56.000000000 +0000 @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.http import HttpRequest -from django.utils import six + +import six # In a separate file to avoid circular imports... diff -Nru django-tastypie-0.13.3/tastypie/compat.py django-tastypie-0.14.3/tastypie/compat.py --- django-tastypie-0.13.3/tastypie/compat.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/compat.py 2019-12-23 15:48:14.000000000 +0000 @@ -2,15 +2,27 @@ import django from django.conf import settings -from django.contrib.auth import get_user_model # flake8: noqa +from django.contrib.auth import get_user_model # noqa - -__all__ = ['get_user_model', 'get_username_field', 'AUTH_USER_MODEL'] +try: + from django.urls import NoReverseMatch, reverse, Resolver404, get_script_prefix # noqa +except ImportError: # 1.8 backwards compat + from django.core.urlresolvers import NoReverseMatch, reverse, Resolver404, get_script_prefix # noqa AUTH_USER_MODEL = settings.AUTH_USER_MODEL +def is_authenticated(user): + """ + Django is changing User.is_authenticated into a property. Calling it + will be deprecated by Django 2.0 and a warning in 1.10+. + """ + if django.VERSION < (1, 10): + return bool(user.is_authenticated()) + return bool(user.is_authenticated) + + def get_username_field(): return get_user_model().USERNAME_FIELD @@ -20,3 +32,25 @@ atomic_decorator = django.db.transaction.atomic + +# Compatability for salted vs unsalted CSRF tokens; +# Django 1.10's _sanitize_token also hashes it, so it can't be compared directly. +# Solution is to call _sanitize_token on both tokens, then unsalt or noop both +try: + from django.middleware.csrf import _unsalt_cipher_token + + def unsalt_token(token): + return _unsalt_cipher_token(token) +except ImportError: + + def unsalt_token(token): + return token + + +# force_text deprecated in 2.2, removed in 3.0 +# note that in 1.1.x, force_str and force_text both exist, but force_str behaves +# much differently on python 3 than python 2. +if django.VERSION < (2, 2): + from django.utils.encoding import force_text as force_str # noqa +else: + from django.utils.encoding import force_str # noqa diff -Nru django-tastypie-0.13.3/tastypie/contrib/contenttypes/resources.py django-tastypie-0.14.3/tastypie/contrib/contenttypes/resources.py --- django-tastypie-0.13.3/tastypie/contrib/contenttypes/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/contrib/contenttypes/resources.py 2017-07-03 14:17:15.000000000 +0000 @@ -2,7 +2,15 @@ from tastypie.bundle import Bundle from tastypie.resources import ModelResource from tastypie.exceptions import NotFound -from django.core.urlresolvers import resolve, Resolver404, get_script_prefix + +try: + from django.urls import resolve, Resolver404, get_script_prefix +except ImportError: + from django.core.urlresolvers import ( + resolve, + Resolver404, + get_script_prefix, + ) class GenericResource(ModelResource): diff -Nru django-tastypie-0.13.3/tastypie/contrib/gis/COPYING django-tastypie-0.14.3/tastypie/contrib/gis/COPYING --- django-tastypie-0.13.3/tastypie/contrib/gis/COPYING 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/contrib/gis/COPYING 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -resources.py contains code from -https://github.com/newsapps/django-boundaryservice/blob/master/boundaryservice/tastyhacks.py: - -Copyright (c) 2011 Chicago Tribune, Christopher Groskopf, Ryan Nagle - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff -Nru django-tastypie-0.13.3/tastypie/exceptions.py django-tastypie-0.14.3/tastypie/exceptions.py --- django-tastypie-0.13.3/tastypie/exceptions.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/exceptions.py 2019-02-28 22:54:58.000000000 +0000 @@ -38,16 +38,36 @@ class ApiFieldError(TastypieError): """ - Raised when there is a configuration error with a ``ApiField``. + Raised when there is a validation error with an ``ApiField``. """ pass class UnsupportedFormat(TastypieError): """ + Raised when an unsupported serialization/deserialization format is requested. + Two more specific subclasses are provided, ``UnsupportedSerializationFormat`` + and ``UnsupportedDeserializationFormat``. + """ + _msg_template = '%s' + + def __init__(self, msg, *args, **kwargs): + msg = self._msg_template % msg + super(UnsupportedFormat, self).__init__(msg, *args, **kwargs) + + +class UnsupportedSerializationFormat(UnsupportedFormat): + """ Raised when an unsupported serialization format is requested. """ - pass + _msg_template = "The format indicated '%s' had no available serialization method. Please check your ``formats`` and ``content_types`` on your Serializer." + + +class UnsupportedDeserializationFormat(UnsupportedFormat): + """ + Raised when an unsupported deserialization format is requested. + """ + _msg_template = "The format indicated '%s' had no available deserialization method. Please check your ``formats`` and ``content_types`` on your Serializer." class BadRequest(TastypieError): @@ -66,7 +86,7 @@ class InvalidFilterError(BadRequest): """ - Raised when the end user attempts to use a filter that has not be + Raised when the end user attempts to use a filter that has not been explicitly allowed. """ pass @@ -74,7 +94,7 @@ class InvalidSortError(BadRequest): """ - Raised when the end user attempts to sort on a field that has not be + Raised when the end user attempts to sort on a field that has not been explicitly allowed. """ pass diff -Nru django-tastypie-0.13.3/tastypie/fields.py django-tastypie-0.14.3/tastypie/fields.py --- django-tastypie-0.13.3/tastypie/fields.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/fields.py 2019-12-09 15:42:22.000000000 +0000 @@ -14,7 +14,9 @@ except ImportError: from django.db.models.fields.related_descriptors import\ ReverseOneToOneDescriptor -from django.utils import datetime_safe, six +from django.utils import datetime_safe + +import six from tastypie.bundle import Bundle from tastypie.exceptions import ApiFieldError, NotFound @@ -128,13 +130,12 @@ current_object = getattr(current_object, attr, None) if current_object is None: - if self.has_default(): - current_object = self._default + if self.null: # Fall out of the loop, given any further attempts at # accesses will fail miserably. break - elif self.null: - current_object = None + elif self.has_default(): + current_object = self._default # Fall out of the loop, given any further attempts at # accesses will fail miserably. break @@ -390,7 +391,7 @@ if isinstance(value, six.string_types): try: year, month, day = value[:10].split('-') - hour, minute, second = value[10:18].split(':') + hour, minute, second = value[11:19].split(':') return make_aware(datetime_safe.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))) except ValueError: @@ -641,11 +642,11 @@ # We also need to check to see if updates are allowed on the FK resource. if not obj and unique_keys: try: - fk_resource.obj_get(fk_bundle, skip_errors=True, **data) + fk_resource.obj_get(fk_bundle, **data) except (ObjectDoesNotExist, NotFound, TypeError): try: # Attempt lookup by primary key - fk_resource.obj_get(fk_bundle, skip_errors=True, **unique_keys) + fk_resource.obj_get(fk_bundle, **unique_keys) except (ObjectDoesNotExist, NotFound): pass except MultipleObjectsReturned: @@ -741,7 +742,7 @@ def contribute_to_class(self, cls, name): super(ToOneField, self).contribute_to_class(cls, name) - if not self.related_name: + if not self.related_name and isinstance(self.attribute, six.string_types): related_field = getattr(self._resource._meta.object_class, self.attribute, None) if isinstance(related_field, ReverseOneToOneDescriptor): # This is the case when we are writing to a reverse one to one field. @@ -851,13 +852,12 @@ except ObjectDoesNotExist: the_m2ms = None - if not the_m2ms: + if the_m2ms is None: break - if not the_m2ms: + if the_m2ms is None: if not self.null: raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr)) - return [] if isinstance(the_m2ms, models.Manager): diff -Nru django-tastypie-0.13.3/tastypie/http.py django-tastypie-0.14.3/tastypie/http.py --- django-tastypie-0.13.3/tastypie/http.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/http.py 2019-02-28 22:54:58.000000000 +0000 @@ -59,6 +59,10 @@ status_code = 405 +class HttpNotAcceptable(HttpResponse): + status_code = 406 + + class HttpConflict(HttpResponse): status_code = 409 @@ -67,6 +71,10 @@ status_code = 410 +class HttpUnsupportedMediaType(HttpResponse): + status_code = 415 + + class HttpUnprocessableEntity(HttpResponse): status_code = 422 diff -Nru django-tastypie-0.13.3/tastypie/__init__.py django-tastypie-0.14.3/tastypie/__init__.py --- django-tastypie-0.13.3/tastypie/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/__init__.py 2019-12-16 15:06:06.000000000 +0000 @@ -3,7 +3,7 @@ __author__ = 'Daniel Lindsley & the Tastypie core team' -VERSION = (0, 13, 3) +VERSION = (0, 14, 3) __short_version__ = '.'.join(map(str, VERSION[0:2])) __version__ = ''.join(['.'.join(map(str, VERSION[0:3])), ''.join(VERSION[3:])]) diff -Nru django-tastypie-0.13.3/tastypie/management/commands/backfill_api_keys.py django-tastypie-0.14.3/tastypie/management/commands/backfill_api_keys.py --- django-tastypie-0.13.3/tastypie/management/commands/backfill_api_keys.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/management/commands/backfill_api_keys.py 2017-06-16 19:06:40.000000000 +0000 @@ -1,14 +1,14 @@ from __future__ import print_function from __future__ import unicode_literals -from django.core.management.base import NoArgsCommand +from django.core.management.base import BaseCommand from tastypie.compat import get_user_model from tastypie.models import ApiKey -class Command(NoArgsCommand): +class Command(BaseCommand): help = "Goes through all users and adds API keys for any that don't have one." - def handle_noargs(self, **options): + def handle(self, **options): "Goes through all users and adds API keys for any that don't have one." self.verbosity = int(options.get('verbosity', 1)) diff -Nru django-tastypie-0.13.3/tastypie/migrations/0001_initial.py django-tastypie-0.14.3/tastypie/migrations/0001_initial.py --- django-tastypie-0.13.3/tastypie/migrations/0001_initial.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/migrations/0001_initial.py 2019-02-28 22:55:01.000000000 +0000 @@ -32,7 +32,7 @@ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('key', models.CharField(default='', max_length=128, db_index=True, blank=True)), ('created', models.DateTimeField(default=tastypie.utils.timezone.now)), - ('user', models.OneToOneField(related_name='api_key', to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(related_name='api_key', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ 'abstract': False, diff -Nru django-tastypie-0.13.3/tastypie/migrations/0002_api_access_url_length.py django-tastypie-0.14.3/tastypie/migrations/0002_api_access_url_length.py --- django-tastypie-0.13.3/tastypie/migrations/0002_api_access_url_length.py 1970-01-01 00:00:00.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/migrations/0002_api_access_url_length.py 2019-02-28 22:55:01.000000000 +0000 @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-24 14:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tastypie', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='apiaccess', + name='url', + field=models.TextField(blank=True, default=''), + ), + ] diff -Nru django-tastypie-0.13.3/tastypie/models.py django-tastypie-0.14.3/tastypie/models.py --- django-tastypie-0.13.3/tastypie/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/models.py 2019-12-09 15:31:56.000000000 +0000 @@ -6,7 +6,7 @@ from django.conf import settings from django.db import models -from django.utils.encoding import python_2_unicode_compatible +from six import python_2_unicode_compatible from tastypie.utils import now @@ -15,7 +15,7 @@ class ApiAccess(models.Model): """A simple model for use with the ``CacheDBThrottle`` behaviors.""" identifier = models.CharField(max_length=255) - url = models.CharField(max_length=255, blank=True, default='') + url = models.TextField(blank=True, default='') request_method = models.CharField(max_length=10, blank=True, default='') accessed = models.PositiveIntegerField() @@ -31,12 +31,13 @@ import uuid from tastypie.compat import AUTH_USER_MODEL + @python_2_unicode_compatible class ApiKey(models.Model): - user = models.OneToOneField(AUTH_USER_MODEL, related_name='api_key') + user = models.OneToOneField(AUTH_USER_MODEL, related_name='api_key', on_delete=models.CASCADE) key = models.CharField(max_length=128, blank=True, default='', db_index=True) created = models.DateTimeField(default=now) - def __unicode__(self): + def __str__(self): return u"%s for %s" % (self.key, self.user) def save(self, *args, **kwargs): diff -Nru django-tastypie-0.13.3/tastypie/paginator.py django-tastypie-0.14.3/tastypie/paginator.py --- django-tastypie-0.13.3/tastypie/paginator.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/paginator.py 2019-12-09 15:31:56.000000000 +0000 @@ -1,7 +1,8 @@ from __future__ import unicode_literals from django.conf import settings -from django.utils import six + +import six from tastypie.exceptions import BadRequest @@ -159,11 +160,10 @@ del request_params['limit'] if 'offset' in request_params: del request_params['offset'] - request_params.update({'limit': limit, 'offset': offset}) + request_params.update({'limit': str(limit), 'offset': str(offset)}) encoded_params = request_params.urlencode() except AttributeError: request_params = {} - for k, v in self.request_data.items(): if isinstance(v, six.text_type): request_params[k] = v.encode('utf-8') diff -Nru django-tastypie-0.13.3/tastypie/resources.py django-tastypie-0.14.3/tastypie/resources.py --- django-tastypie-0.13.3/tastypie/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/resources.py 2019-12-09 15:46:51.000000000 +0000 @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from copy import deepcopy +from copy import copy, deepcopy from datetime import datetime import logging import sys @@ -9,17 +9,14 @@ import warnings from wsgiref.handlers import format_date_time -import django from django.conf import settings from django.conf.urls import url from django.core.exceptions import ( - ObjectDoesNotExist, MultipleObjectsReturned, ValidationError, -) -from django.core.urlresolvers import ( - NoReverseMatch, reverse, Resolver404, get_script_prefix + ObjectDoesNotExist, MultipleObjectsReturned, ValidationError, FieldDoesNotExist ) from django.core.signals import got_request_exception from django.core.exceptions import ImproperlyConfigured +from django.db.models.fields.related import ForeignKey try: from django.contrib.gis.db.models.fields import GeometryField except (ImproperlyConfigured, ImportError): @@ -31,21 +28,24 @@ except ImportError: from django.db.models.fields.related_descriptors import\ ReverseOneToOneDescriptor -from django.db.models.sql.constants import QUERY_TERMS + from django.http import HttpResponse, HttpResponseNotFound, Http404 -from django.utils import six from django.utils.cache import patch_cache_control, patch_vary_headers from django.utils.html import escape from django.views.decorators.csrf import csrf_exempt +import six + from tastypie.authentication import Authentication from tastypie.authorization import ReadOnlyAuthorization from tastypie.bundle import Bundle from tastypie.cache import NoCache +from tastypie.compat import NoReverseMatch, reverse, Resolver404, get_script_prefix from tastypie.constants import ALL, ALL_WITH_RELATIONS from tastypie.exceptions import ( NotFound, BadRequest, InvalidFilterError, HydrationError, InvalidSortError, ImmediateHttpResponse, Unauthorized, UnsupportedFormat, + UnsupportedSerializationFormat, UnsupportedDeserializationFormat, ) from tastypie import fields from tastypie import http @@ -64,7 +64,7 @@ def sanitize(text): # We put the single quotes back, due to their frequent usage in exception # messages. - return escape(text).replace(''', "'").replace('"', '"') + return escape(text).replace(''', "'").replace('"', '"').replace(''', "'") class ResourceOptions(object): @@ -94,7 +94,7 @@ ordering = [] object_class = None queryset = None - fields = [] + fields = None excludes = [] include_resource_uri = True include_absolute_url = False @@ -155,6 +155,7 @@ new_class = super(DeclarativeMetaclass, cls).__new__(cls, name, bases, attrs) opts = getattr(new_class, 'Meta', None) new_class._meta = ResourceOptions(opts) + abstract = getattr(new_class._meta, 'abstract', False) if not getattr(new_class._meta, 'resource_name', None): # No ``resource_name`` provided. Attempt to auto-name the resource. @@ -169,6 +170,11 @@ elif 'resource_uri' in new_class.base_fields and 'resource_uri' not in attrs: del(new_class.base_fields['resource_uri']) + if abstract and 'resource_uri' not in attrs: + # abstract classes don't have resource_uris unless explicitly provided + if 'resource_uri' in new_class.base_fields: + del(new_class.base_fields['resource_uri']) + for field_name, field_object in new_class.base_fields.items(): if hasattr(field_object, 'contribute_to_class'): field_object.contribute_to_class(new_class, field_name) @@ -192,12 +198,14 @@ # TypeError: object.__new__(method-wrapper) is not safe, use method-wrapper.__new__() # when trying to copy a generator used as a default. Wrap call to # generator in lambda to get around this error. - self.fields = deepcopy(self.base_fields) + self.fields = {k: copy(v) for k, v in self.base_fields.items()} if api_name is not None: self._meta.api_name = api_name def __getattr__(self, name): + if name == '__setstate__': + raise AttributeError(name) try: return self.fields[name] except KeyError: @@ -269,43 +277,52 @@ return wrapper + def get_response_class_for_exception(self, request, exception): + """ + Can be overridden to customize response classes used for uncaught + exceptions. Should always return a subclass of + ``django.http.HttpResponse``. + """ + if isinstance(exception, (NotFound, ObjectDoesNotExist, Http404)): + return HttpResponseNotFound + elif isinstance(exception, UnsupportedSerializationFormat): + return http.HttpNotAcceptable + elif isinstance(exception, UnsupportedDeserializationFormat): + return http.HttpUnsupportedMediaType + elif isinstance(exception, UnsupportedFormat): + return http.HttpBadRequest + + return http.HttpApplicationError + def _handle_500(self, request, exception): - the_trace = '\n'.join(traceback.format_exception(*(sys.exc_info()))) - response_class = http.HttpApplicationError - response_code = 500 - - NOT_FOUND_EXCEPTIONS = (NotFound, ObjectDoesNotExist, Http404) - - if isinstance(exception, NOT_FOUND_EXCEPTIONS): - response_class = HttpResponseNotFound - response_code = 404 + the_trace = traceback.format_exception(*sys.exc_info()) + if six.PY2: + the_trace = [ + six.text_type(line, 'utf-8') + for line in the_trace + ] + the_trace = u'\n'.join(the_trace) - elif isinstance(exception, UnsupportedFormat): - response_class = http.HttpBadRequest - response_code = 400 + response_class = self.get_response_class_for_exception(request, exception) if settings.DEBUG: data = { "error_message": sanitize(six.text_type(exception)), "traceback": the_trace, } - return self.error_response(request, data, response_class=response_class) - - # When DEBUG is False, send an error message to the admins (unless it's - # a 404, in which case we check the setting). + else: + data = { + "error_message": getattr(settings, 'TASTYPIE_CANNED_ERROR', "Sorry, this request could not be processed. Please try again later."), + } - if not response_code == 404: + if response_class.status_code >= 500: log = logging.getLogger('django.request.tastypie') log.error('Internal Server Error: %s' % request.path, exc_info=True, - extra={'status_code': response_code, 'request': request}) + extra={'status_code': response_class.status_code, 'request': request}) # Send the signal so other apps are aware of the exception. got_request_exception.send(self.__class__, request=request) - # Prep the data going out. - data = { - "error_message": getattr(settings, 'TASTYPIE_CANNED_ERROR', "Sorry, this request could not be processed. Please try again later."), - } return self.error_response(request, data, response_class=response_class) def _build_reverse_url(self, name, args=None, kwargs=None): @@ -836,12 +853,18 @@ if prefix and chomped_uri.startswith(prefix): chomped_uri = chomped_uri[len(prefix) - 1:] + # We know that we are dealing with a "detail" URI + # Look for the beginning of object key (last meaningful part of the URI) + end_of_resource_name = chomped_uri.rstrip('/').rfind('/') + if end_of_resource_name == -1: + raise NotFound("An incorrect URL was provided '%s' for the '%s' resource." % (uri, self.__class__.__name__)) # We mangle the path a bit further & run URL resolution against *only* - # the current class. This ought to prevent bad URLs from resolving to - # incorrect data. - found_at = chomped_uri.rfind(self._meta.resource_name) - if found_at == -1: + # the current class (but up to detail key). This ought to prevent bad + # URLs from resolving to incorrect data. + split_url = chomped_uri.rstrip('/').rsplit('/', 1)[0] + if not split_url.endswith('/' + self._meta.resource_name): raise NotFound("An incorrect URL was provided '%s' for the '%s' resource." % (uri, self.__class__.__name__)) + found_at = chomped_uri.rfind(self._meta.resource_name, 0, end_of_resource_name) chomped_uri = chomped_uri[found_at:] try: for url_resolver in getattr(self, 'urls', []): @@ -950,12 +973,13 @@ setattr(bundle.obj, field_object.attribute, value.obj) except (ValueError, ObjectDoesNotExist): bundle.related_objects_to_save[field_object.attribute] = value.obj + # not required, not sent + elif field_object.blank and field_name not in bundle.data: + continue elif field_object.null: if not isinstance(getattr(bundle.obj.__class__, field_object.attribute, None), ReverseOneToOneDescriptor): # only update if not a reverse one to one field setattr(bundle.obj, field_object.attribute, value) - elif field_object.blank: - continue return bundle @@ -1047,7 +1071,7 @@ related_type = 'to_one' data['fields'][field_name]['related_type'] = related_type try: - uri = reverse('api_get_schema', kwargs={ + uri = self._build_reverse_url('api_get_schema', kwargs={ 'api_name': self._meta.api_name, 'resource_name': field_object.to_class()._meta.resource_name }) @@ -1412,7 +1436,7 @@ Return ``HttpNoContent`` (204 No Content) if ``Meta.always_return_data = False`` (default). - Return ``HttpAccepted`` (200 OK) if + Return ``HttpResponse`` (200 OK) with new data if ``Meta.always_return_data = True``. """ deserialized = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json')) @@ -1725,8 +1749,11 @@ Returns a serialized list of resources based on the identifiers from the URL. - Calls ``obj_get`` to fetch only the objects requested. This method - only responds to HTTP GET. + Calls ``obj_get_list`` to fetch only the objects requests in + a single query. This method only responds to HTTP GET. + + For backward compatibility the method ``obj_get`` is used if + ``obj_get_list`` is not implemented. Should return a HttpResponse (200 OK). """ @@ -1741,14 +1768,39 @@ not_found = [] base_bundle = self.build_bundle(request=request) - for identifier in obj_identifiers: - try: - obj = self.obj_get(bundle=base_bundle, **{self._meta.detail_uri_name: identifier}) - bundle = self.build_bundle(obj=obj, request=request) - bundle = self.full_dehydrate(bundle, for_list=True) - objects.append(bundle) - except (ObjectDoesNotExist, Unauthorized): - not_found.append(identifier) + # We will try to get a queryset from obj_get_list. + queryset = None + + try: + queryset = self.obj_get_list(bundle=base_bundle).filter( + **{self._meta.detail_uri_name + '__in': obj_identifiers}) + except NotImplementedError: + pass + + if queryset is not None: + # Fetch the objects from the queryset to a dictionary. + objects_dict = {} + for obj in queryset: + objects_dict[str(getattr(obj, self._meta.detail_uri_name))] = obj + + # Walk the list of identifiers in order and get the objects or feed the not_found list. + for identifier in obj_identifiers: + if identifier in objects_dict: + bundle = self.build_bundle(obj=objects_dict[identifier], request=request) + bundle = self.full_dehydrate(bundle, for_list=True) + objects.append(bundle) + else: + not_found.append(identifier) + else: + # Use the old way. + for identifier in obj_identifiers: + try: + obj = self.obj_get(bundle=base_bundle, **{self._meta.detail_uri_name: identifier}) + bundle = self.build_bundle(obj=obj, request=request) + bundle = self.full_dehydrate(bundle, for_list=True) + objects.append(bundle) + except (ObjectDoesNotExist, Unauthorized): + not_found.append(identifier) object_list = { self._meta.collection_name: objects, @@ -1764,23 +1816,40 @@ class ModelDeclarativeMetaclass(DeclarativeMetaclass): def __new__(cls, name, bases, attrs): meta = attrs.get('Meta') + if getattr(meta, 'abstract', False): + # abstract base classes do nothing on declaration + new_class = super(ModelDeclarativeMetaclass, cls).__new__(cls, name, bases, attrs) + return new_class + + # Sanity check: ModelResource needs either a queryset or object_class: + if meta and not hasattr(meta, 'queryset') and not hasattr(meta, 'object_class'): + msg = "ModelResource (%s) requires Meta.object_class or Meta.queryset" + raise ImproperlyConfigured(msg % name) - if meta and hasattr(meta, 'queryset'): + if hasattr(meta, 'queryset') and not hasattr(meta, 'object_class'): setattr(meta, 'object_class', meta.queryset.model) new_class = super(ModelDeclarativeMetaclass, cls).__new__(cls, name, bases, attrs) - include_fields = getattr(new_class._meta, 'fields', []) + specified_fields = getattr(new_class._meta, 'fields', None) excludes = getattr(new_class._meta, 'excludes', []) field_names = list(new_class.base_fields.keys()) + include_fields = specified_fields + + if include_fields is None: + if meta and meta.object_class: + include_fields = [f.name for f in meta.object_class._meta.fields] + else: + include_fields = [] + for field_name in field_names: if field_name == 'resource_uri': continue if field_name in new_class.declared_fields: continue - if len(include_fields) and field_name not in include_fields: + if specified_fields is not None and field_name not in include_fields: del(new_class.base_fields[field_name]) - if len(excludes) and field_name in excludes: + if field_name in excludes: del(new_class.base_fields[field_name]) # Add in the new fields. @@ -1811,8 +1880,13 @@ Given a Django model field, return if it should be included in the contributed ApiFields. """ + if isinstance(field, ForeignKey): + return True # Ignore certain fields (related fields). - if getattr(field, 'rel'): + if hasattr(field, 'remote_field'): + if field.remote_field: + return True + elif getattr(field, 'rel'): return True return False @@ -1836,7 +1910,7 @@ result = fields.FloatField elif internal_type in ('DecimalField',): result = fields.DecimalField - elif internal_type in ('IntegerField', 'PositiveIntegerField', 'PositiveSmallIntegerField', 'SmallIntegerField', 'AutoField'): + elif internal_type in ('IntegerField', 'PositiveIntegerField', 'PositiveSmallIntegerField', 'SmallIntegerField', 'AutoField', 'BigIntegerField'): result = fields.IntegerField elif internal_type in ('FileField', 'ImageField'): result = fields.FileField @@ -1871,11 +1945,11 @@ continue # If field is not present in explicit field listing, skip - if fields and f.name not in fields: + if f.name not in fields: continue # If field is in exclude list, skip - if excludes and f.name in excludes: + if f.name in excludes: continue if cls.should_skip_field(f): @@ -2001,14 +2075,6 @@ qs_filters = {} - if getattr(self._meta, 'queryset', None) is not None: - # Get the possible query terms from the current QuerySet. - query_terms = self._meta.queryset.query.query_terms - else: - query_terms = QUERY_TERMS - if django.VERSION >= (1, 8) and GeometryField: - query_terms = query_terms | set(GeometryField.class_lookups.keys()) - for filter_expr, value in filters.items(): filter_bits = filter_expr.split(LOOKUP_SEP) field_name = filter_bits.pop(0) @@ -2018,6 +2084,16 @@ # It's not a field we know about. Move along citizen. continue + # Validate filter types other than 'exact' that are supported by the field type + try: + django_field_name = self.fields[field_name].attribute + django_field = self._meta.object_class._meta.get_field(django_field_name) + if hasattr(django_field, 'field'): + django_field = django_field.field # related field + except FieldDoesNotExist: + raise InvalidFilterError("The '%s' field is not a valid field name" % field_name) + + query_terms = django_field.get_lookups().keys() if len(filter_bits) and filter_bits[-1] in query_terms: filter_type = filter_bits.pop() @@ -2154,7 +2230,7 @@ if len(object_list) <= 0: raise self._meta.object_class.DoesNotExist("Couldn't find an instance of '%s' which matched '%s'." % (self._meta.object_class.__name__, stringified_kwargs)) elif len(object_list) > 1: - raise MultipleObjectsReturned("More than '%s' matched '%s'." % (self._meta.object_class.__name__, stringified_kwargs)) + raise MultipleObjectsReturned("More than one '%s' matched '%s'." % (self._meta.object_class.__name__, stringified_kwargs)) bundle.obj = object_list[0] self.authorized_read_detail(object_list, bundle) @@ -2181,6 +2257,11 @@ lookup parameters that can find them in the DB """ lookup_kwargs = {} + + # Handle detail_uri_name specially + if self._meta.detail_uri_name in kwargs: + lookup_kwargs[self._meta.detail_uri_name] = kwargs.pop(self._meta.detail_uri_name) + bundle.obj = self.get_object_list(bundle.request).model() # Override data values, we rely on uri identifiers bundle.data.update(kwargs) @@ -2190,10 +2271,6 @@ bundle = self.hydrate(bundle) for identifier in kwargs: - if identifier == self._meta.detail_uri_name: - lookup_kwargs[identifier] = kwargs[identifier] - continue - field_object = self.fields[identifier] # Skip readonly or related fields. @@ -2221,7 +2298,7 @@ if bundle_detail_data is None or (arg_detail_data is not None and str(bundle_detail_data) != str(arg_detail_data)): try: lookup_kwargs = self.lookup_kwargs_with_identifiers(bundle, kwargs) - except: + except: # noqa # if there is trouble hydrating the data, fall back to just # using kwargs by itself (usually it only contains a "pk" key # and this will work fine. @@ -2496,6 +2573,9 @@ Force Django to process the VERB. """ if request.method == verb: + if not hasattr(request, '_read_started'): + request._read_started = False + if hasattr(request, '_post'): del request._post del request._files diff -Nru django-tastypie-0.13.3/tastypie/serializers.py django-tastypie-0.14.3/tastypie/serializers.py --- django-tastypie-0.13.3/tastypie/serializers.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/serializers.py 2019-12-09 15:31:56.000000000 +0000 @@ -6,20 +6,27 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.utils import six -from django.utils.encoding import force_text, smart_bytes +from django.utils.encoding import smart_bytes from django.core.serializers import json as djangojson +import six + from tastypie.bundle import Bundle -from tastypie.exceptions import BadRequest, UnsupportedFormat +from tastypie.compat import force_str +from tastypie.exceptions import BadRequest, UnsupportedSerializationFormat,\ + UnsupportedDeserializationFormat from tastypie.utils import format_datetime, format_date, format_time,\ make_naive + +import warnings try: - import defusedxml.lxml as lxml - from defusedxml.common import DefusedXmlException - from defusedxml.lxml import parse as parse_xml - from lxml.etree import Element, tostring, LxmlError + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + import defusedxml.lxml as lxml + from defusedxml.common import DefusedXmlException + from defusedxml.lxml import parse as parse_xml + from lxml.etree import Element, tostring, LxmlError except ImportError: lxml = None @@ -34,7 +41,7 @@ biplist = None -XML_ENCODING = re.compile('<\?xml.*?\?>', re.IGNORECASE) +XML_ENCODING = re.compile(r'<\?xml.*?\?>', re.IGNORECASE) # Ugh & blah. @@ -69,6 +76,17 @@ Resolver.__init__(self) +def _get_default_formats(): + formats = ['json'] + if lxml: + formats.append('xml') + if yaml: + formats.append('yaml') + if biplist: + formats.append('plist') + return formats + + _NUM = 0 _DICT = 1 _LIST = 2 @@ -107,14 +125,14 @@ * jsonp (Disabled by default) * xml * yaml - * plist (see http://explorapp.com/biplist/) + * plist (see https://bitbucket.org/wooster/biplist) It was designed to make changing behavior easy, either by overridding the various format methods (i.e. ``to_json``), by changing the ``formats/content_types`` options or by altering the other hook methods. """ - formats = ['json', 'xml', 'yaml', 'plist'] + formats = _get_default_formats() content_types = { 'json': 'application/json', @@ -230,8 +248,8 @@ if self.datetime_formatting == 'iso-8601-strict': # Remove microseconds to strictly adhere to iso-8601 data = ( - datetime.datetime.combine(datetime.date(1, 1, 1), data) - - datetime.timedelta(microseconds=data.microsecond) + datetime.datetime.combine(datetime.date(1, 1, 1), data) + - datetime.timedelta(microseconds=data.microsecond) ).time() return data.isoformat() @@ -248,7 +266,7 @@ method = self._to_methods.get(format) if method is None: - raise UnsupportedFormat("The format indicated '%s' had no available serialization method. Please check your ``formats`` and ``content_types`` on your Serializer." % format) + raise UnsupportedSerializationFormat(format) return method(bundle, options) @@ -264,10 +282,10 @@ method = self._from_methods.get(format) if method is None: - raise UnsupportedFormat("The format indicated '%s' had no available deserialization method. Please check your ``formats`` and ``content_types`` on your Serializer." % format) + raise UnsupportedDeserializationFormat(format) if isinstance(content, six.binary_type): - content = force_text(content) + content = force_str(content) return method(content) @@ -299,7 +317,7 @@ to_simple = self.to_simple return {key: to_simple(val, options) for key, val in six.iteritems(data)} if stype == _STR: - return force_text(data) + return force_str(data) if stype == _LIST: to_simple = self.to_simple return [to_simple(item, options) for item in data] @@ -356,7 +374,7 @@ if isinstance(simple_data, six.text_type): element.text = simple_data else: - element.text = force_text(simple_data) + element.text = force_str(simple_data) return element diff -Nru django-tastypie-0.13.3/tastypie/test.py django-tastypie-0.14.3/tastypie/test.py --- django-tastypie-0.13.3/tastypie/test.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/test.py 2019-12-09 15:31:56.000000000 +0000 @@ -1,12 +1,10 @@ from __future__ import unicode_literals import time -import warnings from django.conf import settings -from django.test import TestCase from django.test.client import Client -from django.utils.encoding import force_text +from tastypie.compat import force_str from tastypie.serializers import Serializer @@ -486,7 +484,7 @@ """ self.assertHttpOK(resp) self.assertTrue(resp['Content-Type'].startswith('application/json')) - self.assertValidJSON(force_text(resp.content)) + self.assertValidJSON(force_str(resp.content)) def assertValidXMLResponse(self, resp): """ @@ -499,7 +497,7 @@ """ self.assertHttpOK(resp) self.assertTrue(resp['Content-Type'].startswith('application/xml')) - self.assertValidXML(force_text(resp.content)) + self.assertValidXML(force_str(resp.content)) def assertValidYAMLResponse(self, resp): """ @@ -512,7 +510,7 @@ """ self.assertHttpOK(resp) self.assertTrue(resp['Content-Type'].startswith('text/yaml')) - self.assertValidYAML(force_text(resp.content)) + self.assertValidYAML(force_str(resp.content)) def assertValidPlistResponse(self, resp): """ @@ -525,7 +523,7 @@ """ self.assertHttpOK(resp) self.assertTrue(resp['Content-Type'].startswith('application/x-plist')) - self.assertValidPlist(force_text(resp.content)) + self.assertValidPlist(force_str(resp.content)) def deserialize(self, resp): """ @@ -557,16 +555,3 @@ changes. """ self.assertEqual(sorted(data.keys()), sorted(expected)) - - -class ResourceTestCase(ResourceTestCaseMixin, TestCase): - """ - This class exists for backwards compatibility, from before ResourceTestCaseMixin was added. - It throws a deprecation warning. - """ - def __init__(self, *args, **kwargs): - warnings.warn( - "'ResourceTestCase' is deprecated & will be removed by v1.0.0. " - "Please use ``ResourceTestCaseMixin`` instead. " - "For example: ``class MyTest(ResourceTestCaseMixin, django.test.TestCase):``.") - super(ResourceTestCase, self).__init__(*args, **kwargs) diff -Nru django-tastypie-0.13.3/tastypie/utils/dict.py django-tastypie-0.14.3/tastypie/utils/dict.py --- django-tastypie-0.13.3/tastypie/utils/dict.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/utils/dict.py 2019-12-23 15:48:43.000000000 +0000 @@ -1,6 +1,7 @@ -from django.utils import six from django.utils.encoding import smart_bytes +import six + def dict_strip_unicode_keys(uni_dict): """ diff -Nru django-tastypie-0.13.3/tastypie/utils/formatting.py django-tastypie-0.14.3/tastypie/utils/formatting.py --- django-tastypie-0.13.3/tastypie/utils/formatting.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/utils/formatting.py 2019-03-01 02:40:46.000000000 +0000 @@ -4,7 +4,7 @@ from tastypie.utils.timezone import make_naive, aware_datetime -from dateutil.parser import parse as mk_datetime # flake8: noqa +from dateutil.parser import parse as mk_datetime # noqa def format_datetime(dt): diff -Nru django-tastypie-0.13.3/tastypie/utils/__init__.py django-tastypie-0.14.3/tastypie/utils/__init__.py --- django-tastypie-0.13.3/tastypie/utils/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/utils/__init__.py 2019-03-01 02:40:46.000000000 +0000 @@ -1,8 +1,8 @@ -from tastypie.utils.dict import dict_strip_unicode_keys # flake8: noqa -from tastypie.utils.formatting import mk_datetime, format_datetime, format_date, format_time # flake8: noqa -from tastypie.utils.urls import trailing_slash # flake8: noqa -from tastypie.utils.validate_jsonp import is_valid_jsonp_callback_value # flake8: noqa -from tastypie.utils.timezone import now, make_aware, make_naive, aware_date, aware_datetime # flake8: noqa +from tastypie.utils.dict import dict_strip_unicode_keys # noqa +from tastypie.utils.formatting import mk_datetime, format_datetime, format_date, format_time # noqa +from tastypie.utils.urls import trailing_slash # noqa +from tastypie.utils.validate_jsonp import is_valid_jsonp_callback_value # noqa +from tastypie.utils.timezone import now, make_aware, make_naive, aware_date, aware_datetime # noqa def string_to_python(value): diff -Nru django-tastypie-0.13.3/tastypie/utils/mime.py django-tastypie-0.14.3/tastypie/utils/mime.py --- django-tastypie-0.13.3/tastypie/utils/mime.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/utils/mime.py 2019-02-28 22:55:01.000000000 +0000 @@ -28,7 +28,7 @@ return serializer.get_mime_for_format(format) # If callback parameter is present, use JSONP. - if 'callback' in request.GET: + if 'callback' in request.GET and 'jsonp' in serializer.formats: return serializer.get_mime_for_format('jsonp') # Try to fallback on the Accepts header. diff -Nru django-tastypie-0.13.3/tastypie/utils/urls.py django-tastypie-0.14.3/tastypie/utils/urls.py --- django-tastypie-0.13.3/tastypie/utils/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/utils/urls.py 2019-12-09 15:31:56.000000000 +0000 @@ -1,7 +1,8 @@ from __future__ import unicode_literals from django.conf import settings -from django.utils import six + +import six _trailing_slash = '/?' if getattr(settings, 'TASTYPIE_ALLOW_MISSING_SLASH', False) else '/' @@ -12,4 +13,5 @@ def __call__(self): return self + trailing_slash = CallableUnicode(_trailing_slash) diff -Nru django-tastypie-0.13.3/tastypie/utils/validate_jsonp.py django-tastypie-0.14.3/tastypie/utils/validate_jsonp.py --- django-tastypie-0.13.3/tastypie/utils/validate_jsonp.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/utils/validate_jsonp.py 2019-12-09 15:31:56.000000000 +0000 @@ -9,7 +9,7 @@ from unicodedata import category -from django.utils import six +import six # ----------------------------------------------------------------------------- # javascript identifier unicode categories and "exceptional" chars @@ -81,7 +81,7 @@ if len(segment) < 4: return False try: - add_char(unichr(int('0x' + segment[:4], 16))) + add_char(six.unichr(int('0x' + segment[:4], 16))) except Exception: return False add_char(segment[4:]) @@ -93,13 +93,13 @@ first_char = identifier[0] - if not ((first_char in valid_jsid_chars) or - (ucd_cat(first_char) in valid_jsid_categories_start)): + if not ((first_char in valid_jsid_chars) + or (ucd_cat(first_char) in valid_jsid_categories_start)): return False for char in identifier[1:]: - if not ((char in valid_jsid_chars) or - (ucd_cat(char) in valid_jsid_categories)): + if not ((char in valid_jsid_chars) + or (ucd_cat(char) in valid_jsid_categories)): return False return True @@ -208,6 +208,7 @@ """ + if __name__ == '__main__': import doctest doctest.testmod() diff -Nru django-tastypie-0.13.3/tastypie/validation.py django-tastypie-0.14.3/tastypie/validation.py --- django-tastypie-0.13.3/tastypie/validation.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tastypie/validation.py 2019-04-08 14:29:08.000000000 +0000 @@ -2,6 +2,7 @@ from django.core.exceptions import ImproperlyConfigured from django.forms import ModelForm from django.forms.models import model_to_dict +from django.db.models.fields.related import RelatedField class Validation(object): @@ -45,6 +46,13 @@ super(FormValidation, self).__init__(**kwargs) def form_args(self, bundle): + ''' + Use the model data to generate the form arguments to be used for + validation. In the case of fields that had to be hydrated (such as + FK relationships), be sure to use the hydrated value (comes from + model_to_dict()) rather than the value in bundle.data, since the latter + would likely not validate as the form won't expect a URI. + ''' data = bundle.data # Ensure we get a bound Form, regardless of the state of the bundle. @@ -52,14 +60,22 @@ data = {} kwargs = {'data': {}} - if hasattr(bundle.obj, 'pk'): if issubclass(self.form_class, ModelForm): kwargs['instance'] = bundle.obj kwargs['data'] = model_to_dict(bundle.obj) - - kwargs['data'].update(data) + # iterate over the fields in the object and find those that are + # related fields - FK, M2M, O2M, etc. In those cases, we need + # to *not* use the data in the bundle, since it is a URI to a + # resource. Instead, use the output of model_to_dict for + # validation, since that is already properly hydrated. + for field in bundle.obj._meta.fields: + if field.name in bundle.data: + if not isinstance(field, RelatedField): + kwargs['data'][field.name] = bundle.data[field.name] + else: + kwargs['data'].update(data) return kwargs def is_valid(self, bundle, request=None): diff -Nru django-tastypie-0.13.3/tests/alphanumeric/api/resources.py django-tastypie-0.14.3/tests/alphanumeric/api/resources.py --- django-tastypie-0.13.3/tests/alphanumeric/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -from tastypie.authorization import Authorization -from tastypie.resources import ModelResource -from alphanumeric.models import Product - - -class ProductResource(ModelResource): - class Meta: - resource_name = 'products' - queryset = Product.objects.all() - authorization = Authorization() diff -Nru django-tastypie-0.13.3/tests/alphanumeric/api/urls.py django-tastypie-0.14.3/tests/alphanumeric/api/urls.py --- django-tastypie-0.13.3/tests/alphanumeric/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -from tastypie.api import Api -from alphanumeric.api.resources import ProductResource - - -api = Api(api_name='v1') -api.register(ProductResource(), canonical=True) - -urlpatterns = api.urls diff -Nru django-tastypie-0.13.3/tests/alphanumeric/fixtures/test_data.json django-tastypie-0.14.3/tests/alphanumeric/fixtures/test_data.json --- django-tastypie-0.13.3/tests/alphanumeric/fixtures/test_data.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/fixtures/test_data.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -[ - { - "fields": { - "name": "Skateboardrampe", - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "11111" - }, - { - "fields": { - "name": "Bigwheel", - "created": "2010-05-04 20:05:00", - "updated": "2010-05-04 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "76123" - }, - { - "fields": { - "name": "Trampolin", - "created": "2010-05-04 20:05:00", - "updated": "2010-05-04 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "WS65150-01" - }, - { - "fields": { - "name": "Laufrad", - "created": "2010-05-04 20:05:00", - "updated": "2010-05-04 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "65100A-01" - }, - { - "fields": { - "name": "Bigwheel", - "created": "2010-05-04 20:05:00", - "updated": "2010-05-04 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "76123/01" - }, - { - "fields": { - "name": "Human Hamsterball", - "created": "2010-05-04 20:05:00", - "updated": "2010-05-04 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "WS65150/01-01" - }, - { - "fields": { - "name": "Ant Farm", - "created": "2010-05-04 20:05:00", - "updated": "2010-05-04 20:05:00" - }, - "model": "alphanumeric.product", - "pk": "WS77.86" - } -] diff -Nru django-tastypie-0.13.3/tests/alphanumeric/models.py django-tastypie-0.14.3/tests/alphanumeric/models.py --- django-tastypie-0.13.3/tests/alphanumeric/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -from django.db import models -from tastypie.utils import now - - -class Product(models.Model): - artnr = models.CharField(max_length=8, primary_key=True) - name = models.CharField(max_length=32, null=False, blank=True, default='') - created = models.DateTimeField(default=now) - updated = models.DateTimeField(default=now) - - def __unicode__(self): - return "%s - %s" % (self.artnr, self.name) - - def save(self, *args, **kwargs): - self.updated = now() - return super(Product, self).save(*args, **kwargs) diff -Nru django-tastypie-0.13.3/tests/alphanumeric/tests/http.py django-tastypie-0.14.3/tests/alphanumeric/tests/http.py --- django-tastypie-0.13.3/tests/alphanumeric/tests/http.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/tests/http.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,145 +0,0 @@ -import json - -from django.test.utils import override_settings -from django.utils import six - -from testcases import TestServerTestCase - -try: - from http.client import HTTPConnection -except ImportError: - from httplib import HTTPConnection - - -def header_name(name): - if six.PY3: - return name - else: - return name.lower() - - -@override_settings(DEBUG=True) -class HTTPTestCase(TestServerTestCase): - def setUp(self): - self.start_test_server(address='localhost', port=8001) - - def tearDown(self): - self.stop_test_server() - - def get_connection(self): - return HTTPConnection('localhost', 8001) - - def test_get_apis_json(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200, data) - self.assertEqual(data, '{"products": {"list_endpoint": "/api/v1/products/", "schema": "/api/v1/products/schema/"}}') - - def test_get_apis_xml(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'application/xml'}) - response = connection.getresponse() - connection.close() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200, data) - self.assertEqual(data, '\n/api/v1/products//api/v1/products/schema/') - - def test_get_list(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/products/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200, data) - expected = { - 'meta': { - 'previous': None, - 'total_count': 7, - 'offset': 0, - 'limit': 20, - 'next': None - }, - 'objects': [ - { - 'updated': '2010-03-30T20:05:00', - 'resource_uri': '/api/v1/products/11111/', - 'name': 'Skateboardrampe', - 'artnr': '11111', - 'created': '2010-03-30T20:05:00' - }, - { - 'updated': '2010-05-04T20:05:00', - 'resource_uri': '/api/v1/products/76123/', - 'name': 'Bigwheel', - 'artnr': '76123', - 'created': '2010-05-04T20:05:00' - }, - { - 'updated': '2010-05-04T20:05:00', - 'resource_uri': '/api/v1/products/WS65150-01/', - 'name': 'Trampolin', - 'artnr': 'WS65150-01', - 'created': '2010-05-04T20:05:00' - }, - { - 'updated': '2010-05-04T20:05:00', - 'resource_uri': '/api/v1/products/65100A-01/', - 'name': 'Laufrad', - 'artnr': '65100A-01', - 'created': '2010-05-04T20:05:00' - }, - { - 'updated': '2010-05-04T20:05:00', - 'resource_uri': '/api/v1/products/76123/01/', - 'name': 'Bigwheel', - 'artnr': '76123/01', - 'created': '2010-05-04T20:05:00' - }, - { - 'updated': '2010-05-04T20:05:00', - 'resource_uri': '/api/v1/products/WS65150/01-01/', - 'name': 'Human Hamsterball', - 'artnr': 'WS65150/01-01', - 'created': '2010-05-04T20:05:00' - }, - { - 'updated': '2010-05-04T20:05:00', - 'resource_uri': '/api/v1/products/WS77.86/', - 'name': 'Ant Farm', - 'artnr': 'WS77.86', - 'created': '2010-05-04T20:05:00' - } - ] - } - - resp = json.loads(data) - - # testing separately to help locate issues - self.assertEqual(resp['meta'], expected['meta']) - self.assertEqual(resp['objects'], expected['objects']) - - def test_post_object(self): - connection = self.get_connection() - post_data = '{"artnr": "A76124/03", "name": "Bigwheel XXL"}' - connection.request('POST', '/api/v1/products/', body=post_data, headers={'Accept': 'application/json', 'Content-type': 'application/json'}) - response = connection.getresponse() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 201, data) - location = dict(response.getheaders())[header_name('Location')] - self.assertTrue(location.endswith('/api/v1/products/A76124/03/')) - - # make sure posted object exists - connection.request('GET', '/api/v1/products/A76124/03/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200, data) - - obj = json.loads(data) - - self.assertEqual(obj['name'], 'Bigwheel XXL') - self.assertEqual(obj['artnr'], 'A76124/03') diff -Nru django-tastypie-0.13.3/tests/alphanumeric/tests/__init__.py django-tastypie-0.14.3/tests/alphanumeric/tests/__init__.py --- django-tastypie-0.13.3/tests/alphanumeric/tests/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -from alphanumeric.tests.views import * # flake8: noqa -from alphanumeric.tests.http import * # flake8: noqa diff -Nru django-tastypie-0.13.3/tests/alphanumeric/tests/views.py django-tastypie-0.14.3/tests/alphanumeric/tests/views.py --- django-tastypie-0.13.3/tests/alphanumeric/tests/views.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/tests/views.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,171 +0,0 @@ -import json - -from django.http import HttpRequest - -from testcases import TestCaseWithFixture - - -class ViewsTestCase(TestCaseWithFixture): - def test_gets(self): - resp = self.client.get('/api/v1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(deserialized['products'], {'list_endpoint': '/api/v1/products/', 'schema': '/api/v1/products/schema/'}) - - resp = self.client.get('/api/v1/products/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['meta']['limit'], 20) - self.assertEqual(len(deserialized['objects']), 7) - self.assertEqual([obj['name'] for obj in deserialized['objects']], - [u'Skateboardrampe', u'Bigwheel', u'Trampolin', u'Laufrad', u'Bigwheel', u'Human Hamsterball', u'Ant Farm']) - - resp = self.client.get('/api/v1/products/11111/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['name'], u'Skateboardrampe') - - resp = self.client.get('/api/v1/products/set/11111;76123/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['name'] for obj in deserialized['objects']], [u'Skateboardrampe', u'Bigwheel']) - - # Same tests with \w+ instead of \d+ for primary key regexp: - resp = self.client.get('/api/v1/products/WS65150-01/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['name'], u'Trampolin') - - resp = self.client.get('/api/v1/products/set/WS65150-01;65100A-01/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['name'] for obj in deserialized['objects']], [u'Trampolin', u'Laufrad']) - - # And now Slashes - resp = self.client.get('/api/v1/products/76123/01/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['name'], u'Bigwheel') - - resp = self.client.get('/api/v1/products/set/76123/01;65100A-01/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['name'] for obj in deserialized['objects']], [u'Bigwheel', u'Laufrad']) - - resp = self.client.get('/api/v1/products/WS65150/01-01/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['name'], u'Human Hamsterball') - - resp = self.client.get('/api/v1/products/set/76123/01;WS65150/01-01/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['name'] for obj in deserialized['objects']], [u'Bigwheel', u'Human Hamsterball']) - - # And now dots - resp = self.client.get('/api/v1/products/WS77.86/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['name'], u'Ant Farm') - - # slashes, and more dots - resp = self.client.get('/api/v1/products/set/76123/01;WS77.86/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['name'] for obj in deserialized['objects']], [u'Bigwheel', u'Ant Farm']) - - def test_posts(self): - request = HttpRequest() - post_data = '{"name": "Ball", "artnr": "12345"}' - request._body = post_data - - resp = self.client.post('/api/v1/products/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/products/12345/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/products/12345/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['name'], 'Ball') - self.assertEqual(obj['artnr'], '12345') - - # With appended characters - request = HttpRequest() - post_data = '{"name": "Ball 2", "artnr": "12345ABC"}' - request._body = post_data - - resp = self.client.post('/api/v1/products/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/products/12345ABC/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/products/12345ABC/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['name'], 'Ball 2') - self.assertEqual(obj['artnr'], '12345ABC') - - # With prepended characters - request = HttpRequest() - post_data = '{"name": "Ball 3", "artnr": "WK12345"}' - request._body = post_data - - resp = self.client.post('/api/v1/products/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/products/WK12345/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/products/WK12345/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['name'], 'Ball 3') - self.assertEqual(obj['artnr'], 'WK12345') - - # Now Primary Keys with Slashes - request = HttpRequest() - post_data = '{"name": "Bigwheel", "artnr": "76123/03"}' - request._body = post_data - - resp = self.client.post('/api/v1/products/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/products/76123/03/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/products/76123/03/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['name'], 'Bigwheel') - self.assertEqual(obj['artnr'], '76123/03') - - request = HttpRequest() - post_data = '{"name": "Trampolin", "artnr": "WS65150/02"}' - request._body = post_data - - resp = self.client.post('/api/v1/products/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/products/WS65150/02/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/products/WS65150/02/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['name'], 'Trampolin') - self.assertEqual(obj['artnr'], 'WS65150/02') diff -Nru django-tastypie-0.13.3/tests/alphanumeric/urls.py django-tastypie-0.14.3/tests/alphanumeric/urls.py --- django-tastypie-0.13.3/tests/alphanumeric/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/alphanumeric/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -from django.conf.urls import include, url - - -urlpatterns = [ - url(r'^api/', include('alphanumeric.api.urls')), -] diff -Nru django-tastypie-0.13.3/tests/authorization/api/resources.py django-tastypie-0.14.3/tests/authorization/api/resources.py --- django-tastypie-0.13.3/tests/authorization/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/authorization/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,92 +0,0 @@ -from django.contrib.auth.models import User -from django.contrib.sites.models import Site -from django.core.exceptions import ObjectDoesNotExist -from tastypie.authentication import BasicAuthentication -from tastypie.authorization import Authorization -from tastypie.exceptions import Unauthorized -from tastypie import fields -from tastypie.resources import ModelResource -from ..models import AuthorProfile, Article - - -class PerUserAuthorization(Authorization): - def create_list(self, object_list, bundle): - return object_list - - def create_detail(self, object_list, bundle): - return True - - def update_list(self, object_list, bundle): - revised_list = [] - - for article in object_list: - for profile in article.authors.all(): - if bundle.request.user.pk == profile.user.pk: - revised_list.append(article) - - return revised_list - - def update_detail(self, object_list, bundle): - if getattr(bundle.obj, 'pk', None): - try: - object_list.get(pk=bundle.obj.pk) - - for profile in bundle.obj.authors.all(): - if bundle.request.user.pk == profile.user.pk: - return True - except ObjectDoesNotExist: - pass - - # Fallback on the data sent. - for profile in bundle.data['authors']: - if hasattr(profile, 'keys'): - if bundle.request.user.pk == profile['user'].get('id'): - return True - else: - # Ghetto. - if bundle.request.user.pk == profile.strip('/').split('/')[-1]: - return True - - raise Unauthorized("Nope.") - - def delete_list(self, object_list, bundle): - # Disallow deletes completely. - raise Unauthorized("Nope.") - - def delete_detail(self, object_list, bundle): - # Disallow deletes completely. - raise Unauthorized("Nope.") - - -class UserResource(ModelResource): - class Meta: - queryset = User.objects.all() - authentication = BasicAuthentication() - authorization = Authorization() - excludes = ['email', 'password', 'is_staff', 'is_superuser'] - - -class SiteResource(ModelResource): - class Meta: - queryset = Site.objects.all() - authentication = BasicAuthentication() - authorization = Authorization() - - -class AuthorProfileResource(ModelResource): - user = fields.ToOneField(UserResource, 'user', full=True) - sites = fields.ToManyField(SiteResource, 'sites', related_name='author_profiles', full=True) - - class Meta: - queryset = AuthorProfile.objects.all() - authentication = BasicAuthentication() - authorization = Authorization() - - -class ArticleResource(ModelResource): - authors = fields.ToManyField(AuthorProfileResource, 'authors', related_name='articles', full=True) - - class Meta: - queryset = Article.objects.all() - authentication = BasicAuthentication() - authorization = PerUserAuthorization() diff -Nru django-tastypie-0.13.3/tests/authorization/models.py django-tastypie-0.14.3/tests/authorization/models.py --- django-tastypie-0.13.3/tests/authorization/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/authorization/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -from django.contrib.auth.models import User -from django.contrib.sites.models import Site -from django.db import models -from django.template.defaultfilters import slugify -from tastypie.utils.timezone import now - - -class AuthorProfile(models.Model): - user = models.OneToOneField(User, related_name='author_profile') - short_bio = models.CharField(max_length=255, blank=True, default='') - bio = models.TextField(blank=True, default='') - # We'll use the ``sites`` the author is assigned to as a way to control - # the permissions. - sites = models.ManyToManyField(Site, related_name='author_profiles') - - def __unicode__(self): - return u"Profile: {0}".format(self.user.username) - - -class Article(models.Model): - # We'll also use the ``authors`` to control perms. - authors = models.ManyToManyField(AuthorProfile, related_name='articles') - title = models.CharField(max_length=255) - slug = models.SlugField(blank=True) - content = models.TextField(blank=True, default='') - added_on = models.DateTimeField(default=now) - - def __unicode__(self): - return u"{0} - {1}".format(self.title, self.slug) - - def save(self, *args, **kwargs): - if not self.slug: - self.slug = slugify(self.title) - - return super(Article, self).save(*args, **kwargs) diff -Nru django-tastypie-0.13.3/tests/authorization/tests.py django-tastypie-0.14.3/tests/authorization/tests.py --- django-tastypie-0.13.3/tests/authorization/tests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/authorization/tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,249 +0,0 @@ -import json -import mock - -from django.contrib.auth.models import User -from django.contrib.sites.models import Site -from tastypie.test import ResourceTestCase -from .models import AuthorProfile, Article -from .api.resources import PerUserAuthorization - - -# If `./run_all_tests.sh authorization` is run, ret_false might never get called -# and some tests will fail, but if `./run_all_tests.sh authorization.tests` is -# run they'll pass. -def ret_false(*args): - return False - - -class PerUserAuthorizationTestCase(ResourceTestCase): - def setUp(self): - super(PerUserAuthorizationTestCase, self).setUp() - - # Make some data. - self.site_1 = Site.objects.create( - domain='superawesomenewssite.com', - name='Super Awesome News Site' - ) - self.site_2 = Site.objects.create( - domain='snarkynewssite.com', - name='Snarky News Site' - ) - - self.user_1 = User.objects.create_user('mr_author', 'mister_author@example.com', 'pass') - self.user_2 = User.objects.create_user('mrs_author', 'missus_author@example.com', 'pass') - self.user_3 = User.objects.create_user('ms_editor', 'miss_editor@example.com', 'pass') - - self.author_profile_1 = AuthorProfile.objects.create( - user=self.user_1, - short_bio="Just a dude writing stories for Super Awesome.", - bio="Just a dude writing stories for Super Awesome. Life is good." - ) - self.author_profile_2 = AuthorProfile.objects.create( - user=self.user_2, - short_bio="A highly professional woman writing for Snarky.", - bio="Way better educated than that schmuck writing for Super Awesome. " - ) - self.author_profile_3 = AuthorProfile.objects.create( - user=self.user_3, - short_bio="I wish my writers used spellcheck.", - bio="Whatever." - ) - self.author_profile_1.sites.add(self.site_1) - self.author_profile_2.sites.add(self.site_2) - self.author_profile_3.sites.add(self.site_1, self.site_2) - - self.article_1 = Article.objects.create( - title='New Stuff Announced Today!', - content="Some big tech company announced new stuffs! Go get your consumerism on!" - ) - self.article_1.authors.add(self.author_profile_1, self.author_profile_3) - - self.article_2 = Article.objects.create( - title='Editorial: Why stuff is great', - content="Because you can buy buy buy & fill the gaping voids in your life." - ) - self.article_2.authors.add(self.author_profile_3) - - self.article_3 = Article.objects.create( - title='Ugh, Who Cares About New Stuff?', - content="Obviously stuff by other by other company is way better." - ) - self.article_3.authors.add(self.author_profile_2, self.author_profile_3) - - # Auth bits. - self.author_auth_1 = self.create_basic('mr_author', 'pass') - self.author_auth_2 = self.create_basic('mrs_author', 'pass') - self.author_auth_3 = self.create_basic('ms_editor', 'pass') - - # URIs. - self.article_uri_1 = '/api/v1/article/{0}/'.format(self.article_1.pk) - self.article_uri_2 = '/api/v1/article/{0}/'.format(self.article_2.pk) - self.article_uri_3 = '/api/v1/article/{0}/'.format(self.article_3.pk) - self.author_uri_1 = '/api/v1/authorprofile/{0}/'.format(self.author_profile_1.pk) - self.author_uri_2 = '/api/v1/authorprofile/{0}/'.format(self.author_profile_2.pk) - self.author_uri_3 = '/api/v1/authorprofile/{0}/'.format(self.author_profile_3.pk) - - def test_get_list(self): - # Should be all articles. - resp = self.api_client.get('/api/v1/article/', format='json', authentication=self.author_auth_1) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 3) - first_article = self.deserialize(resp)['objects'][0] - self.assertEqual(first_article['id'], self.article_1.pk) - self.assertEqual(len(first_article['authors']), 2) - - # Should ALSO be all articles. - resp = self.api_client.get('/api/v1/article/', format='json', authentication=self.author_auth_2) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 3) - first_article = self.deserialize(resp)['objects'][0] - self.assertEqual(first_article['id'], self.article_1.pk) - self.assertEqual(len(first_article['authors']), 2) - - def test_get_detail(self): - # Should be all articles. - resp = self.api_client.get(self.article_uri_1, format='json', authentication=self.author_auth_1) - self.assertValidJSONResponse(resp) - first_article = self.deserialize(resp) - self.assertKeys(first_article, ['added_on', 'authors', 'content', 'id', 'resource_uri', 'slug', 'title']) - self.assertEqual(first_article['id'], self.article_1.pk) - - # Should ALSO be all articles, even though it's not our article. - resp = self.api_client.get(self.article_uri_1, format='json', authentication=self.author_auth_2) - self.assertValidJSONResponse(resp) - first_article = self.deserialize(resp) - self.assertKeys(first_article, ['added_on', 'authors', 'content', 'id', 'resource_uri', 'slug', 'title']) - self.assertEqual(first_article['id'], self.article_1.pk) - - # Should ALSO ALSO be all articles, even though it's not our article. - resp = self.api_client.get(self.article_uri_2, format='json', authentication=self.author_auth_1) - self.assertValidJSONResponse(resp) - second_article = self.deserialize(resp) - self.assertKeys(second_article, ['added_on', 'authors', 'content', 'id', 'resource_uri', 'slug', 'title']) - self.assertEqual(second_article['id'], self.article_2.pk) - - @mock.patch.object(PerUserAuthorization, "read_detail", ret_false) - def test_get_unauthorized_detail(self): - resp = self.api_client.get(self.article_uri_1, format='json', authentication=self.author_auth_1) - self.assertHttpUnauthorized(resp) - - def test_post_list(self): - # Should be able to create with reckless abandon. - self.assertEqual(Article.objects.count(), 3) - self.assertHttpCreated(self.api_client.post('/api/v1/article/', format='json', data={ - 'title': 'Yet Another Story', - 'content': 'Stuff.', - 'authors': [self.author_uri_1], - }, authentication=self.author_auth_1)) - # Verify a new one has been added. - self.assertEqual(Article.objects.count(), 4) - - # Should ALSO be able to create with reckless abandon. - self.assertHttpCreated(self.api_client.post('/api/v1/article/', format='json', data={ - 'title': 'Even Another Story', - 'content': 'This time, with competent words.', - 'authors': [self.author_uri_2], - }, authentication=self.author_auth_2)) - # Verify a new one has been added. - self.assertEqual(Article.objects.count(), 5) - - @mock.patch.object(PerUserAuthorization, "create_detail", ret_false) - def test_post_unauthorized_detail(self): - resp = self.api_client.post('/api/v1/article/', format='json', data={ - 'title': 'Yet Another Story', - 'content': 'Stuff.', - 'authors': [self.author_uri_1], - }, authentication=self.author_auth_1) - self.assertHttpUnauthorized(resp) - - def test_put_list(self): - resp = self.api_client.get('/api/v1/article/', format='json', authentication=self.author_auth_2) - self.assertHttpOK(resp) - the_data = json.loads(resp.content.decode('utf-8')) - - # Tweak the data. - the_data['objects'][0]['title'] = 'This is edited.' - the_data['objects'][2]['title'] = 'Updated: {0}'.format(the_data['objects'][2]['title']) - - # Editor can edit whatever, since they're on all the articles. - self.assertEqual(Article.objects.count(), 3) - self.assertHttpAccepted(self.api_client.put('/api/v1/article/', format='json', data=the_data, authentication=self.author_auth_3)) - # Verify no change in count. - self.assertEqual(Article.objects.count(), 3) - self.assertEqual(Article.objects.get(pk=self.article_1.pk).title, 'This is edited.') - self.assertEqual(Article.objects.get(pk=self.article_1.pk).content, 'Some big tech company announced new stuffs! Go get your consumerism on!') - self.assertEqual(Article.objects.get(pk=self.article_3.pk).title, 'Updated: Ugh, Who Cares About New Stuff?') - self.assertEqual(Article.objects.get(pk=self.article_3.pk).content, 'Obviously stuff by other by other company is way better.') - - # But a regular author can't update the whole list. - the_data['objects'][2]['title'] = "Your Story Is Bad And You Should Feel Bad" - del the_data['objects'][0] - self.assertHttpUnauthorized(self.api_client.put('/api/v1/article/', format='json', data=the_data, authentication=self.author_auth_1)) - # Verify count goes down. - self.assertEqual(Article.objects.count(), 2) - # Verify he couldn't edit that title. - self.assertEqual(Article.objects.get(pk=self.article_3.pk).title, 'Updated: Ugh, Who Cares About New Stuff?') - - def test_put_detail(self): - # Should be able to update our story. - self.assertEqual(Article.objects.count(), 3) - self.assertHttpAccepted(self.api_client.put(self.article_uri_1, format='json', data={ - 'title': 'Revised Story', - 'content': "We didn't like the previous version.", - 'authors': [self.author_uri_1], - }, authentication=self.author_auth_1)) - # Verify no change in count. - self.assertEqual(Article.objects.count(), 3) - self.assertEqual(Article.objects.get(pk=self.article_1.pk).title, 'Revised Story') - self.assertEqual(Article.objects.get(pk=self.article_1.pk).content, "We didn't like the previous version.") - - # But CAN'T update one we don't have authorship of. - self.assertHttpUnauthorized(self.api_client.put(self.article_uri_2, format='json', data={ - 'title': 'Ha, Her Story Was Bad', - 'content': "And she didn't share a bagel with me this morning.", - 'authors': [self.author_uri_1], - }, authentication=self.author_auth_2)) - # Verify no change in count. - self.assertEqual(Article.objects.count(), 3) - # Verify no change in content - self.assertEqual(Article.objects.get(pk=self.article_2.pk).title, 'Editorial: Why stuff is great') - self.assertEqual(Article.objects.get(pk=self.article_2.pk).content, 'Because you can buy buy buy & fill the gaping voids in your life.') - - @mock.patch.object(PerUserAuthorization, "update_detail", ret_false) - def test_put_unauthorized_detail(self): - resp = self.api_client.put(self.article_uri_1, format='json', data={ - 'title': 'Revised Story', - 'content': "We didn't like the previous version.", - 'authors': [self.author_uri_1], - }, authentication=self.author_auth_1) - self.assertHttpUnauthorized(resp) - - def test_delete_list(self): - # Never a delete, not even once. - self.assertEqual(Article.objects.count(), 3) - self.assertHttpUnauthorized(self.api_client.delete('/api/v1/article/', format='json', authentication=self.author_auth_1)) - self.assertEqual(Article.objects.count(), 3) - - self.assertHttpUnauthorized(self.api_client.delete('/api/v1/article/', format='json', authentication=self.author_auth_2)) - self.assertEqual(Article.objects.count(), 3) - - self.assertHttpUnauthorized(self.api_client.delete('/api/v1/article/', format='json', authentication=self.author_auth_3)) - self.assertEqual(Article.objects.count(), 3) - - def test_delete_detail(self): - # Never a delete, not even once. - self.assertEqual(Article.objects.count(), 3) - self.assertHttpUnauthorized(self.api_client.delete(self.article_uri_1, format='json', authentication=self.author_auth_1)) - self.assertEqual(Article.objects.count(), 3) - - self.assertHttpUnauthorized(self.api_client.delete(self.article_uri_1, format='json', authentication=self.author_auth_2)) - self.assertEqual(Article.objects.count(), 3) - - self.assertHttpUnauthorized(self.api_client.delete(self.article_uri_1, format='json', authentication=self.author_auth_3)) - self.assertEqual(Article.objects.count(), 3) - - @mock.patch.object(PerUserAuthorization, "delete_detail", ret_false) - def test_delete_unauthorized_detail(self): - self.assertEqual(Article.objects.count(), 3) - self.assertHttpUnauthorized(self.api_client.delete(self.article_uri_1, format='json', authentication=self.author_auth_1)) - self.assertEqual(Article.objects.count(), 3) diff -Nru django-tastypie-0.13.3/tests/authorization/urls.py django-tastypie-0.14.3/tests/authorization/urls.py --- django-tastypie-0.13.3/tests/authorization/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/authorization/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -from django.conf.urls import url, include - -from tastypie.api import Api - -from .api.resources import ArticleResource, AuthorProfileResource, SiteResource, UserResource - - -v1_api = Api() -v1_api.register(ArticleResource()) -v1_api.register(AuthorProfileResource()) -v1_api.register(SiteResource()) -v1_api.register(UserResource()) - -urlpatterns = [ - url(r'^api/', include(v1_api.urls)), -] diff -Nru django-tastypie-0.13.3/tests/basic/api/resources.py django-tastypie-0.14.3/tests/basic/api/resources.py --- django-tastypie-0.13.3/tests/basic/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -from django.contrib.auth.models import User -from tastypie.cache import SimpleCache -from tastypie import fields -from tastypie.resources import ModelResource -from tastypie.authentication import SessionAuthentication -from tastypie.authorization import Authorization -from basic.models import Note, AnnotatedNote, SlugBasedNote - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - authorization = Authorization() - - -class CachedUserResource(ModelResource): - class Meta: - allowed_methods = ('get', ) - queryset = User.objects.all() - resource_name = 'cached_users' - cache = SimpleCache(timeout=3600) - - -class PublicCachedUserResource(ModelResource): - class Meta: - allowed_methods = ('get', ) - queryset = User.objects.all() - resource_name = 'public_cached_users' - cache = SimpleCache(timeout=3600, public=True) - - -class CacheDisabledUserResource(ModelResource): - class Meta: - allowed_methods = ('get', ) - queryset = User.objects.all() - resource_name = 'cache_disabled_users' - cache = SimpleCache(timeout=0) - - -class PrivateCachedUserResource(ModelResource): - class Meta: - allowed_methods = ('get', ) - queryset = User.objects.all() - resource_name = 'private_cached_users' - cache = SimpleCache(timeout=3600, private=True) - - -class NoteResource(ModelResource): - user = fields.ForeignKey(UserResource, 'user') - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - authorization = Authorization() - - -class BustedResource(ModelResource): - class Meta: - queryset = AnnotatedNote.objects.all() - resource_name = 'busted' - - def get_list(self, *args, **kwargs): - raise Exception("It's broke.") - - -class SlugBasedNoteResource(ModelResource): - class Meta: - queryset = SlugBasedNote.objects.all() - resource_name = 'slugbased' - detail_uri_name = 'slug' - authorization = Authorization() - - -class SessionUserResource(ModelResource): - class Meta: - resource_name = 'sessionusers' - queryset = User.objects.all() - authentication = SessionAuthentication() - authorization = Authorization() diff -Nru django-tastypie-0.13.3/tests/basic/api/urls.py django-tastypie-0.14.3/tests/basic/api/urls.py --- django-tastypie-0.13.3/tests/basic/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -from tastypie.api import Api -from basic.api.resources import NoteResource, UserResource, BustedResource,\ - CachedUserResource, PublicCachedUserResource, PrivateCachedUserResource,\ - CacheDisabledUserResource, SlugBasedNoteResource, SessionUserResource - - -api = Api(api_name='v1') -api.register(NoteResource(), canonical=True) -api.register(UserResource(), canonical=True) -api.register(CachedUserResource(), canonical=True) -api.register(CacheDisabledUserResource(), canonical=True) -api.register(PublicCachedUserResource(), canonical=True) -api.register(PrivateCachedUserResource(), canonical=True) - -v2_api = Api(api_name='v2') -v2_api.register(BustedResource(), canonical=True) -v2_api.register(SlugBasedNoteResource()) -v2_api.register(SessionUserResource()) - -urlpatterns = v2_api.urls + api.urls diff -Nru django-tastypie-0.13.3/tests/basic/fixtures/test_data.json django-tastypie-0.14.3/tests/basic/fixtures/test_data.json --- django-tastypie-0.13.3/tests/basic/fixtures/test_data.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/fixtures/test_data.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -[ - { - "fields": { - "username": "johndoe", - "email": "john@doe.com", - "password": "this_is_not_a_valid_password_string" - }, - "model": "auth.user", - "pk": 1 - }, - - { - "fields": { - "user": 1, - "title": "First Post!", - "slug": "first-post", - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "is_active": true, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "basic.note", - "pk": 1 - }, - { - "fields": { - "user": 1, - "title": "Another Post", - "slug": "another-post", - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "is_active": true, - "created": "2010-03-31 20:05:00", - "updated": "2010-03-31 20:05:00" - }, - "model": "basic.note", - "pk": 2 - }, - { - "fields": { - "title": "First Post", - "content": "But not before the cat ate the hamster.", - "is_active": true - }, - "model": "basic.slugbasednote", - "pk": "first-post" - }, - { - "fields": { - "title": "Another First Post", - "content": "So it's a hamster-in-a-cat-in-a-dog kinda situation.", - "is_active": true - }, - "model": "basic.slugbasednote", - "pk": "another-first-post" - } -] diff -Nru django-tastypie-0.13.3/tests/basic/models.py django-tastypie-0.14.3/tests/basic/models.py --- django-tastypie-0.13.3/tests/basic/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -from django.contrib.auth.models import User -from django.db import models -from django import forms -from tastypie.utils import now - - -class Note(models.Model): - user = models.ForeignKey(User, related_name='notes') - title = models.CharField(max_length=255) - slug = models.SlugField() - content = models.TextField() - is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=now) - updated = models.DateTimeField(default=now) - - def __unicode__(self): - return self.title - - def save(self, *args, **kwargs): - self.updated = now() - return super(Note, self).save(*args, **kwargs) - - -class AnnotatedNote(models.Model): - note = models.OneToOneField(Note, related_name='annotated') - annotations = models.TextField() - - def __unicode__(self): - title = None - try: - title = self.note.title - except Note.DoesNotExist: - pass - - return u"Annotated %s" % title - - -class SlugBasedNote(models.Model): - slug = models.SlugField(primary_key=True) - title = models.CharField(max_length=255) - content = models.TextField() - is_active = models.BooleanField(default=True) - - def __unicode__(self): - return u"SlugBased %s" % self.title - - -class UserForm(forms.ModelForm): - class Meta: - model = User - exclude = [] diff -Nru django-tastypie-0.13.3/tests/basic/tests/http.py django-tastypie-0.14.3/tests/basic/tests/http.py --- django-tastypie-0.13.3/tests/basic/tests/http.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/tests/http.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,166 +0,0 @@ -import json - -from django.test.utils import override_settings -from django.utils import six - -from testcases import TestServerTestCase - -try: - from http.client import HTTPConnection -except ImportError: - from httplib import HTTPConnection - - -def header_name(name): - if six.PY3: - return name - else: - return name.lower() - - -@override_settings(DEBUG=True) -class HTTPTestCase(TestServerTestCase): - def setUp(self): - self.start_test_server(address='localhost', port=8001) - - def tearDown(self): - self.stop_test_server() - - def get_connection(self): - return HTTPConnection('localhost', 8001) - - def test_get_apis_json(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200) - self.assertEqual(data, '{"cache_disabled_users": {"list_endpoint": "/api/v1/cache_disabled_users/", "schema": "/api/v1/cache_disabled_users/schema/"}, "cached_users": {"list_endpoint": "/api/v1/cached_users/", "schema": "/api/v1/cached_users/schema/"}, "notes": {"list_endpoint": "/api/v1/notes/", "schema": "/api/v1/notes/schema/"}, "private_cached_users": {"list_endpoint": "/api/v1/private_cached_users/", "schema": "/api/v1/private_cached_users/schema/"}, "public_cached_users": {"list_endpoint": "/api/v1/public_cached_users/", "schema": "/api/v1/public_cached_users/schema/"}, "users": {"list_endpoint": "/api/v1/users/", "schema": "/api/v1/users/schema/"}}') - - def test_get_apis_invalid_accept(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'invalid'}) - response = connection.getresponse() - connection.close() - response.read().decode('utf-8') - self.assertEqual(response.status, 400, "Invalid HTTP Accept headers should return HTTP 400") - - def test_get_resource_invalid_accept(self): - """Invalid HTTP Accept headers should return HTTP 400""" - # We need to test this twice as there's a separate dispatch path for resources: - - connection = self.get_connection() - connection.request('GET', '/api/v1/notes/', headers={'Accept': 'invalid'}) - response = connection.getresponse() - connection.close() - response.read().decode('utf-8') - self.assertEqual(response.status, 400, "Invalid HTTP Accept headers should return HTTP 400") - - def test_get_apis_xml(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'application/xml'}) - response = connection.getresponse() - connection.close() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200) - self.assertEqual(data, '\n/api/v1/cache_disabled_users//api/v1/cache_disabled_users/schema//api/v1/cached_users//api/v1/cached_users/schema//api/v1/notes//api/v1/notes/schema//api/v1/private_cached_users//api/v1/private_cached_users/schema//api/v1/public_cached_users//api/v1/public_cached_users/schema//api/v1/users//api/v1/users/schema/') - - def test_get_list(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/notes/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - self.assertEqual(response.read().decode('utf-8'), '{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 2}, "objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00", "user": "/api/v1/users/1/"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00", "user": "/api/v1/users/1/"}]}') - - def test_post_object(self): - connection = self.get_connection() - post_data = '{"content": "A new post.", "is_active": true, "title": "New Title", "slug": "new-title", "user": "/api/v1/users/1/"}' - connection.request('POST', '/api/v1/notes/', body=post_data, headers={'Accept': 'application/json', 'Content-type': 'application/json'}) - response = connection.getresponse() - self.assertEqual(response.status, 201) - location = dict(response.getheaders())[header_name('Location')] - self.assertTrue(location.endswith('/api/v1/notes/3/')) - - # make sure posted object exists - connection.request('GET', '/api/v1/notes/3/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - - self.assertEqual(response.status, 200) - - data = response.read().decode('utf-8') - obj = json.loads(data) - - self.assertEqual(obj['content'], 'A new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - - def test_vary_accept(self): - """ - Ensure that resources return the Vary: Accept header. - """ - connection = self.get_connection() - connection.request('GET', '/api/v1/cached_users/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - - self.assertEqual(response.status, 200) - - headers = dict(response.getheaders()) - vary = headers.get(header_name('Vary'), "") - vary_types = [x.strip().lower() for x in vary.split(",") if x.strip()] - self.assertIn("accept", vary_types) - - def test_cache_control(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/cached_users/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - headers = dict(response.getheaders()) - cache_control = set([x.strip().lower() for x in headers[header_name("Cache-Control")].split(",") if x.strip()]) - - self.assertEqual(cache_control, set(["s-maxage=3600", "max-age=3600"])) - self.assertTrue('"johndoe"' in response.read().decode('utf-8')) - - def test_cache_disabled_control(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/cache_disabled_users/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - headers = dict(response.getheaders()) - cache_control = set([x.strip().lower() for x in headers[header_name("Cache-Control")].split(",") if x.strip()]) - - self.assertEqual(cache_control, set(["s-maxage=0", "max-age=0"])) - self.assertTrue('"johndoe"' in response.read().decode('utf-8')) - - def test_public_cache_control(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/public_cached_users/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - headers = dict(response.getheaders()) - cache_control = set([x.strip().lower() for x in headers[header_name("Cache-Control")].split(",") if x.strip()]) - - self.assertEqual(cache_control, set(["s-maxage=3600", "max-age=3600", "public"])) - self.assertTrue('"johndoe"' in response.read().decode('utf-8')) - - def test_private_cache_control(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/private_cached_users/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - headers = dict(response.getheaders()) - cache_control = set([x.strip().lower() for x in headers[header_name("Cache-Control")].split(",") if x.strip()]) - - self.assertEqual(cache_control, set(["s-maxage=3600", "max-age=3600", "private"])) - self.assertTrue('"johndoe"' in response.read().decode('utf-8')) diff -Nru django-tastypie-0.13.3/tests/basic/tests/__init__.py django-tastypie-0.14.3/tests/basic/tests/__init__.py --- django-tastypie-0.13.3/tests/basic/tests/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -from basic.tests.http import * # flake8: noqa -from basic.tests.resources import * # flake8: noqa -from basic.tests.views import * # flake8: noqa diff -Nru django-tastypie-0.13.3/tests/basic/tests/resources.py django-tastypie-0.14.3/tests/basic/tests/resources.py --- django-tastypie-0.13.3/tests/basic/tests/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/tests/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -from django.contrib.auth.models import User -from django.http import HttpRequest -from tastypie.fields import ToOneField, ToManyField -from tastypie.resources import ModelResource -from basic.api.resources import SlugBasedNoteResource -from basic.models import Note, AnnotatedNote, SlugBasedNote -from testcases import TestCaseWithFixture - - -class InvalidLazyUserResource(ModelResource): - notes = ToManyField('basic.api.resources.FooResource', 'notes') - - class Meta: - queryset = User.objects.all() - - -class NoPathLazyUserResource(ModelResource): - notes = ToManyField('FooResource', 'notes') - - class Meta: - queryset = User.objects.all() - - -class LazyUserResource(ModelResource): - notes = ToManyField('basic.tests.resources.NoteResource', 'notes') - - class Meta: - queryset = User.objects.all() - api_name = 'foo' - - -class NoteResource(ModelResource): - class Meta: - queryset = Note.objects.all() - - -class AnnotatedNoteResource(ModelResource): - class Meta: - queryset = AnnotatedNote.objects.all() - - -class NoteWithAnnotationsResource(ModelResource): - annotated = ToOneField(AnnotatedNoteResource, 'annotated', null=True) - - class Meta: - queryset = Note.objects.all() - - -class NoteModelResourceTestCase(TestCaseWithFixture): - def test_init(self): - resource_1 = NoteResource() - self.assertEqual(len(resource_1.fields), 8) - self.assertNotEqual(resource_1._meta.queryset, None) - self.assertEqual(resource_1._meta.resource_name, 'note') - - # TextFields should have ``default=''`` to match Django's behavior, - # even though that's not what is on the field proper. - self.assertEqual(resource_1.fields['content'].default, '') - - def test_lazy_relations(self): - ilur = InvalidLazyUserResource() - nplur = NoPathLazyUserResource() - lur = LazyUserResource() - - self.assertEqual(ilur.notes.to, 'basic.api.resources.FooResource') - self.assertEqual(nplur.notes.to, 'FooResource') - self.assertEqual(lur.notes.to, 'basic.tests.resources.NoteResource') - - with self.assertRaises(ImportError): - ilur.notes.to_class() - - with self.assertRaises(ImportError): - nplur.notes.to_class() - - to_class = lur.notes.to_class() - self.assertTrue(isinstance(to_class, NoteResource)) - # This is important, as without passing on the ``api_name``, URL - # reversals will fail. Fakes the instance as ``None``, since for - # testing purposes, we don't care. - related = lur.notes.get_related_resource(None) - self.assertEqual(related._meta.api_name, 'foo') - - -class AnnotatedNoteModelResourceTestCase(TestCaseWithFixture): - def test_one_to_one_regression(self): - # Make sure bits don't completely blow up if the related model - # is gone. - n1 = Note.objects.get(pk=1) - - resource_1 = NoteWithAnnotationsResource() - n1_bundle = resource_1.build_bundle(obj=n1) - resource_1.full_dehydrate(n1_bundle) - - -class DetailURIKwargsResourceTestCase(TestCaseWithFixture): - def test_correct_detail_uri_model(self): - n1 = Note.objects.get(pk=1) - - resource = NoteWithAnnotationsResource() - self.assertEqual(resource.detail_uri_kwargs(n1), { - 'pk': 1, - }) - - def test_correct_detail_uri_bundle(self): - n1 = Note.objects.get(pk=1) - - resource = NoteWithAnnotationsResource() - n1_bundle = resource.build_bundle(obj=n1) - self.assertEqual(resource.detail_uri_kwargs(n1_bundle), { - 'pk': 1, - }) - - def test_correct_slug_detail_uri_model(self): - n1 = SlugBasedNote.objects.get(pk='first-post') - - resource = SlugBasedNoteResource() - self.assertEqual(resource.detail_uri_kwargs(n1), { - 'slug': 'first-post', - }) - - def test_correct_slug_detail_uri_bundle(self): - n1 = SlugBasedNote.objects.get(pk='first-post') - - resource = SlugBasedNoteResource() - n1_bundle = resource.build_bundle(obj=n1) - self.assertEqual(resource.detail_uri_kwargs(n1_bundle), { - 'slug': 'first-post', - }) - - -class SlugBasedResourceTestCase(TestCaseWithFixture): - def setUp(self): - super(SlugBasedResourceTestCase, self).setUp() - self.n1 = SlugBasedNote.objects.get(pk='first-post') - self.request = HttpRequest() - self.request.method = 'PUT' - self.resource = SlugBasedNoteResource() - self.n1_bundle = self.resource.build_bundle(obj=self.n1) - - def test_bundle_unique_field(self): - self.assertEqual(self.resource.get_bundle_detail_data(self.n1_bundle), u'first-post') - - def test_obj_update(self): - bundle = self.resource.build_bundle(obj=self.n1, data={ - 'title': 'Foo!', - }) - updated_bundle = self.resource.obj_update(bundle, slug='first-post') - self.assertEqual(updated_bundle.obj.slug, 'first-post') - self.assertEqual(updated_bundle.obj.title, 'Foo!') - - # Again, without the PK this time. - self.n1.slug = None - bundle = self.resource.build_bundle(obj=self.n1, data={ - 'title': 'Bar!', - }) - updated_bundle_2 = self.resource.obj_update(bundle, slug='first-post') - self.assertEqual(updated_bundle_2.obj.slug, 'first-post') - self.assertEqual(updated_bundle_2.obj.title, 'Bar!') - - def test_update_in_place(self): - new_data = { - 'slug': u'foo', - 'title': u'Foo!', - } - new_bundle = self.resource.update_in_place(self.request, self.n1_bundle, new_data) - # Check for updated data. - self.assertEqual(new_bundle.obj.title, u'Foo!') - self.assertEqual(new_bundle.obj.slug, u'foo') - # Make sure it looked up the right instance, even though we didn't - # hand it a PK... - self.assertEqual(new_bundle.obj.pk, self.n1_bundle.obj.pk) - - def test_rollback(self): - bundles = [ - self.n1_bundle - ] - self.resource.rollback(bundles) - - # Make sure it's gone. - self.assertRaises(SlugBasedNote.DoesNotExist, SlugBasedNote.objects.get, pk='first-post') - - -class BundleTestCase(TestCaseWithFixture): - def test_bundle_repr(self): - # __repr__ should return string type (str in PY2 or unicode in PY3) - n = Note.objects.get(pk=1) - - resource = NoteWithAnnotationsResource() - n1_bundle = resource.build_bundle(obj=n) - self.assertTrue(isinstance(repr(n1_bundle), str)) - - data_dict = { - u'∆ключ∆': 1, - 'привет©®': 2 - } - n2_bundle = resource.build_bundle(obj=n, data=data_dict) - self.assertTrue(isinstance(repr(n2_bundle), str)) diff -Nru django-tastypie-0.13.3/tests/basic/tests/views.py django-tastypie-0.14.3/tests/basic/tests/views.py --- django-tastypie-0.13.3/tests/basic/tests/views.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/tests/views.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,164 +0,0 @@ -import json - -from django.contrib.auth.models import User -from django.test import Client - -from testcases import TestCaseWithFixture - - -class ViewsTestCase(TestCaseWithFixture): - def setUp(self): - super(ViewsTestCase, self).setUp() - - def test_gets(self): - resp = self.client.get('/api/v1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 6) - self.assertEqual(deserialized['notes'], {'list_endpoint': '/api/v1/notes/', 'schema': '/api/v1/notes/schema/'}) - - resp = self.client.get('/api/v1/notes/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['meta']['limit'], 20) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'First Post!', u'Another Post']) - - resp = self.client.get('/api/v1/notes/1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 9) - self.assertEqual(deserialized['title'], u'First Post!') - - resp = self.client.get('/api/v1/notes/set/2;1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'Another Post', u'First Post!']) - - def test_get_test_client_error(self): - # The test server should re-raise exceptions to make debugging easier. - self.assertRaises(Exception, self.client.get, '/api/v2/busted/', data={'format': 'json'}) - - def test_posts(self): - post_data = b'{"content": "A new post.", "is_active": true, "title": "New Title", "slug": "new-title", "user": "/api/v1/users/1/"}' - - resp = self.client.post('/api/v1/notes/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/notes/3/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/notes/3/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['content'], 'A new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - - def test_puts(self): - post_data = '{"content": "Another new post.", "is_active": true, "title": "Another New Title", "slug": "new-title", "user": "/api/v1/users/1/"}' - - resp = self.client.put('/api/v1/notes/1/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 204) - - # make sure posted object exists - resp = self.client.get('/api/v1/notes/1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['content'], 'Another new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - - def test_api_field_error(self): - # When a field error is encountered, we should be presenting the message - # back to the user. - post_data = '{"content": "More internet memes.", "is_active": true, "title": "IT\'S OVER 9000!", "slug": "its-over", "user": "/api/v1/users/9001/"}' - - resp = self.client.post('/api/v1/notes/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 400) - self.assertEqual( - json.loads(resp.content.decode('utf-8')), - { - "error": "Could not find the provided users object via resource URI \'/api/v1/users/9001/\'." - } - ) - - def test_invalid_json_error(self): - # When the given data is not valid JSON a readable error message should be returned. - post_data = '{"content": "More internet memes.", "is_active": true, "title": "IT\'S OVER 9000!", "slug": "its-over",' - - resp = self.client.post('/api/v1/notes/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 400) - self.assertEqual( - json.loads(resp.content.decode('utf-8')), - { - "error": "Request is not valid JSON." - } - ) - - def test_options(self): - resp = self.client.options('/api/v1/notes/') - self.assertEqual(resp.status_code, 200) - allows = 'GET,POST,PUT,DELETE,PATCH' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - resp = self.client.options('/api/v1/notes/1/') - self.assertEqual(resp.status_code, 200) - allows = 'GET,POST,PUT,DELETE,PATCH' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - resp = self.client.options('/api/v1/notes/schema/') - self.assertEqual(resp.status_code, 200) - allows = 'GET' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - resp = self.client.options('/api/v1/notes/set/2;1/') - self.assertEqual(resp.status_code, 200) - allows = 'GET' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - def test_slugbased(self): - resp = self.client.get('/api/v2/slugbased/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['meta']['limit'], 20) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'First Post', u'Another First Post']) - - resp = self.client.get('/api/v2/slugbased/first-post/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['title'], u'First Post') - - resp = self.client.get('/api/v2/slugbased/set/another-first-post;first-post/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'Another First Post', u'First Post']) - - def test_session_auth(self): - csrf_client = Client(enforce_csrf_checks=True) - User.objects.create_superuser('daniel', 'daniel@example.com', 'pass') - - # Unauthenticated. - resp = csrf_client.get('/api/v2/sessionusers/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 401) - - # Now log in. - self.assertTrue(csrf_client.login(username='daniel', password='pass')) - # Fake the cookie the login didn't create. :( - csrf_client.cookies['csrftoken'] = 'o9nXqnrypI9ydKoiWGCjDDcxXI7qRymH' - - resp = csrf_client.get('/api/v2/sessionusers/', data={'format': 'json'}, HTTP_X_CSRFTOKEN='o9nXqnrypI9ydKoiWGCjDDcxXI7qRymH') - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) diff -Nru django-tastypie-0.13.3/tests/basic/urls.py django-tastypie-0.14.3/tests/basic/urls.py --- django-tastypie-0.13.3/tests/basic/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/basic/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -from django.conf.urls import include, url - - -urlpatterns = [ - url(r'^api/', include('basic.api.urls')), -] diff -Nru django-tastypie-0.13.3/tests/content_gfk/api/resources.py django-tastypie-0.14.3/tests/content_gfk/api/resources.py --- django-tastypie-0.13.3/tests/content_gfk/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -from tastypie.authorization import Authorization -from tastypie.contrib.contenttypes.fields import GenericForeignKeyField -from tastypie.resources import ModelResource -from content_gfk.models import Note, Quote, Definition, Rating - - -class DefinitionResource(ModelResource): - - class Meta: - resource_name = 'definitions' - queryset = Definition.objects.all() - - -class NoteResource(ModelResource): - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - - -class QuoteResource(ModelResource): - - class Meta: - resource_name = 'quotes' - queryset = Quote.objects.all() - - -class RatingResource(ModelResource): - content_object = GenericForeignKeyField({ - Note: NoteResource, - Quote: QuoteResource - }, 'content_object') - - class Meta: - resource_name = 'ratings' - queryset = Rating.objects.all() - authorization = Authorization() diff -Nru django-tastypie-0.13.3/tests/content_gfk/api/urls.py django-tastypie-0.14.3/tests/content_gfk/api/urls.py --- django-tastypie-0.13.3/tests/content_gfk/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -from tastypie.api import Api -from content_gfk.api.resources import NoteResource, QuoteResource,\ - RatingResource, DefinitionResource - - -api = Api(api_name='v1') -api.register(NoteResource()) -api.register(QuoteResource()) -api.register(RatingResource()) -api.register(DefinitionResource()) - -urlpatterns = api.urls diff -Nru django-tastypie-0.13.3/tests/content_gfk/models.py django-tastypie-0.14.3/tests/content_gfk/models.py --- django-tastypie-0.13.3/tests/content_gfk/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -from django.db import models -from django.contrib.contenttypes.fields import GenericForeignKey -from django.contrib.contenttypes.models import ContentType - - -class Definition(models.Model): - word = models.CharField(max_length=255) - content = models.TextField() - - -class Note(models.Model): - title = models.CharField(max_length=255) - content = models.TextField() - - -class Quote(models.Model): - byline = models.CharField(max_length=255) - content = models.TextField() - - -class Rating(models.Model): - RATINGS = [(x, x) for x in range(1, 6)] - - rating = models.PositiveIntegerField(choices=RATINGS, default=3) - content_type = models.ForeignKey(ContentType) - object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') diff -Nru django-tastypie-0.13.3/tests/content_gfk/tests/fields.py django-tastypie-0.14.3/tests/content_gfk/tests/fields.py --- django-tastypie-0.13.3/tests/content_gfk/tests/fields.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/tests/fields.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,83 +0,0 @@ -from django.test import TestCase - -from tastypie.contrib.contenttypes.fields import GenericForeignKeyField - -from content_gfk.models import Note, Quote, Definition -from content_gfk.api.resources import NoteResource, QuoteResource - - -class ContentTypeFieldTestCase(TestCase): - - def test_init(self): - # Test that you have to use a dict some other resources - with self.assertRaises(ValueError): - GenericForeignKeyField(((Note, NoteResource)), 'nofield') - - # Test that you must register some other resources - with self.assertRaises(ValueError): - GenericForeignKeyField({}, 'nofield') - - # Test that the resources you raise must be models - with self.assertRaises(ValueError): - GenericForeignKeyField({NoteResource: Note}, 'nofield') - - def test_get_related_resource(self): - gfk_field = GenericForeignKeyField({ - Note: NoteResource, - Quote: QuoteResource - }, 'nofield') - - definition_1 = Definition.objects.create( - word='toast', - content="Cook or brown (food, esp. bread or cheese)" - ) - - # Test that you can not link to a model that does not have a resource - with self.assertRaises(TypeError): - gfk_field.get_related_resource(definition_1) - - note_1 = Note.objects.create( - title='All aboard the rest train', - content='Sometimes it is just better to lorem ipsum' - ) - - self.assertTrue(isinstance(gfk_field.get_related_resource(note_1), NoteResource)) - - def test_resource_from_uri(self): - note_2 = Note.objects.create( - title='Generic and such', - content='Sometimes it is to lorem ipsum' - ) - - gfk_field = GenericForeignKeyField({ - Note: NoteResource, - Quote: QuoteResource - }, 'nofield') - - self.assertEqual( - gfk_field.resource_from_uri( - gfk_field.to_class(), - '/api/v1/notes/%s/' % note_2.pk - ).obj, - note_2 - ) - - def test_build_related_resource(self): - gfk_field = GenericForeignKeyField({ - Note: NoteResource, - Quote: QuoteResource - }, 'nofield') - - quote_1 = Quote.objects.create( - byline='Issac Kelly', - content='To ipsum or not to ipsum, that is the cliche' - ) - qr = QuoteResource() - qr.build_bundle(obj=quote_1) - - bundle = gfk_field.build_related_resource( - '/api/v1/quotes/%s/' % quote_1.pk - ) - - # Test that the GFK field builds the same as the QuoteResource - self.assertEqual(bundle.obj, quote_1) diff -Nru django-tastypie-0.13.3/tests/content_gfk/tests/__init__.py django-tastypie-0.14.3/tests/content_gfk/tests/__init__.py --- django-tastypie-0.13.3/tests/content_gfk/tests/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -import warnings -warnings.simplefilter('ignore', Warning) - -from content_gfk.tests.fields import * # flake8: noqa -from content_gfk.tests.resources import * # flake8: noqa diff -Nru django-tastypie-0.13.3/tests/content_gfk/tests/resources.py django-tastypie-0.14.3/tests/content_gfk/tests/resources.py --- django-tastypie-0.13.3/tests/content_gfk/tests/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/tests/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -from django.test import TestCase -from tastypie.exceptions import NotFound -from tastypie.contrib.contenttypes.resources import GenericResource - -from content_gfk.api.resources import NoteResource, DefinitionResource - - -class GenericResourceTestCase(TestCase): - def setUp(self): - self.resource = GenericResource([NoteResource, DefinitionResource]) - - def test_bad_uri(self): - bad_uri = '/bad_uri/' - self.assertRaises(NotFound, self.resource.get_via_uri, bad_uri) - - def test_resource_not_registered(self): - bad_uri = '/api/v1/quotes/1/' - self.assertRaises(NotFound, self.resource.get_via_uri, bad_uri) diff -Nru django-tastypie-0.13.3/tests/content_gfk/urls.py django-tastypie-0.14.3/tests/content_gfk/urls.py --- django-tastypie-0.13.3/tests/content_gfk/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/content_gfk/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -from django.conf.urls import include, url - - -urlpatterns = [ - url(r'^api/', include('content_gfk.api.urls')), -] diff -Nru django-tastypie-0.13.3/tests/core/fixtures/note_testdata.json django-tastypie-0.14.3/tests/core/fixtures/note_testdata.json --- django-tastypie-0.13.3/tests/core/fixtures/note_testdata.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/fixtures/note_testdata.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,174 +0,0 @@ -[ - { - "fields": { - "username": "johndoe", - "email": "john@doe.com", - "password": "abc123" - }, - "model": "auth.user", - "pk": 1 - }, - { - "fields": { - "username": "janedoe", - "email": "jane@doe.com", - "password": "def456" - }, - "model": "auth.user", - "pk": 2 - }, - { - "fields": { - "username": "bobdoe", - "email": "bob@doe.com", - "password": "ghi789", - "is_active": false - }, - "model": "auth.user", - "pk": 3 - }, - { - "fields": { - "user": 1, - "key": "269d2b44e4f90ec8894ac2a31790fd97616792d6", - "created": "2016-02-14T06:22:43.298Z" - }, - "model": "tastypie.apikey", - "pk": 1 - }, - { - "fields": { - "user": 2, - "key": "269d2b44e4f90ec8894ac2a31790fd97616792d7", - "created": "2016-02-14T06:22:43.298Z" - }, - "model": "tastypie.apikey", - "pk": 2 - }, - { - "fields": { - "user": 3, - "key": "269d2b44e4f90ec8894ac2a31790fd97616792d8", - "created": "2016-02-14T06:22:43.298Z" - }, - "model": "tastypie.apikey", - "pk": 3 - }, - { - "fields": { - "date": "2012-09-07", - "username": "MARAUJOP", - "message": "hello" - }, - "model": "core.daterecord", - "pk": 1 - }, - - { - "fields": { - "author": 1, - "title": "First Post!", - "slug": "first-post", - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "is_active": true, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "core.note", - "pk": 1 - }, - { - "fields": { - "author": 1, - "title": "Another Post", - "slug": "another-post", - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "is_active": true, - "created": "2010-03-31 20:05:00", - "updated": "2010-03-31 20:05:00" - }, - "model": "core.note", - "pk": 2 - }, - { - "fields": { - "author": 2, - "title": "Hello World!", - "slug": "hello-world", - "content": "On second though, not sure if I'm ready to share this with the world.", - "is_active": false, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "core.note", - "pk": 3 - }, - { - "fields": { - "author": 2, - "title": "Recent Volcanic Activity.", - "slug": "recent-volcanic-activity", - "content": "My neighborhood's been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "is_active": true, - "created": "2010-04-01 20:05:00", - "updated": "2010-04-01 20:05:00" - }, - "model": "core.note", - "pk": 4 - }, - { - "fields": { - "author": 1, - "title": "My favorite new show", - "slug": "my-favorite-new-show", - "content": "I found an awesome new show on TV. It's about vampires and pancakes and the strong love between them that the wagon is trying to break up.", - "is_active": false, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "core.note", - "pk": 5 - }, - { - "fields": { - "author": 2, - "title": "Granny's Gone", - "slug": "grannys-gone", - "content": "Man, the second eruption came on fast. Granny didn't have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "is_active": true, - "created": "2010-04-02 10:05:00", - "updated": "2010-04-02 10:05:00" - }, - "model": "core.note", - "pk": 6 - }, - - { - "fields": { - "note": 1, - "title": "Funny Cat Picture", - "image": "lulz/catz.gif" - }, - "model": "core.mediabit", - "pk": 1 - }, - - { - "fields": { - "name": "Signups", - "slug": "signups", - "count": 1 - }, - "model": "core.counter", - "pk": 1 - }, - { - "fields": { - "name": "Logins", - "slug": "logins", - "count": 4 - }, - "model": "core.counter", - "pk": 2 - } -] diff -Nru django-tastypie-0.13.3/tests/core/forms.py django-tastypie-0.14.3/tests/core/forms.py --- django-tastypie-0.13.3/tests/core/forms.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/forms.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -from django import forms -from core.models import Note - - -class NoteForm(forms.ModelForm): - foobaz = forms.CharField() - - class Meta: - model = Note - - -class VeryCustomNoteForm(NoteForm): - class Meta: - model = Note - fields = ['title', 'content', 'created', 'is_active', 'foobaz'] - - -# Notes: -# * VeryCustomNoteForm will ONLY have the four listed fields. -# * VeryCustomNoteForm does NOT inherit the ``foobaz`` field from it's -# parent class (unless manually specified). diff -Nru django-tastypie-0.13.3/tests/core/models.py django-tastypie-0.14.3/tests/core/models.py --- django-tastypie-0.13.3/tests/core/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,111 +0,0 @@ -from itertools import count -import uuid - -from django.contrib.auth.models import User -from django.db import models - -from tastypie.utils import now, aware_datetime - - -class DateRecord(models.Model): - date = models.DateField() - username = models.CharField(max_length=20) - message = models.CharField(max_length=20) - - -class Note(models.Model): - author = models.ForeignKey(User, related_name='notes', blank=True, null=True) - title = models.CharField("The Title", max_length=100) - slug = models.SlugField() - content = models.TextField(blank=True) - is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=now) - updated = models.DateTimeField(default=now) - - def __unicode__(self): - return self.title - - def save(self, *args, **kwargs): - self.updated = now() - return super(Note, self).save(*args, **kwargs) - - def what_time_is_it(self): - return aware_datetime(2010, 4, 1, 0, 48) - - def get_absolute_url(self): - return '/some/fake/path/%s/' % self.pk - - @property - def my_property(self): - return 'my_property' - - -class NoteWithEditor(Note): - editor = models.ForeignKey(User, related_name='notes_edited') - - -class Subject(models.Model): - notes = models.ManyToManyField(Note, related_name='subjects') - name = models.CharField(max_length=255) - url = models.URLField() - created = models.DateTimeField(default=now) - - def __unicode__(self): - return self.name - - -class MediaBit(models.Model): - note = models.ForeignKey(Note, related_name='media_bits') - title = models.CharField(max_length=32) - image = models.FileField(blank=True, null=True, upload_to='bits/') - - def __unicode__(self): - return self.title - - -class AutoNowNote(models.Model): - # Purposely a bit more complex to test correct introspection. - title = models.CharField(max_length=100) - slug = models.SlugField(unique=True) - content = models.TextField(blank=True) - is_active = models.BooleanField(default=True) - created = models.DateTimeField(auto_now_add=now, null=True) - updated = models.DateTimeField(auto_now=now) - - def __unicode__(self): - return self.title - - -class Counter(models.Model): - name = models.CharField(max_length=30) - slug = models.SlugField(unique=True) - count = models.PositiveIntegerField(default=0) - - def __unicode__(self): - return self.name - - -int_source = count(1) - - -class MyDefaultPKModel(models.Model): - id = models.IntegerField(primary_key=True, default=lambda: next(int_source), editable=False) - content = models.TextField(blank=True, default='') - - -if hasattr(models, 'UUIDField'): - class MyUUIDModel(models.Model): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - anotheruuid = models.UUIDField(default=uuid.uuid4) - content = models.TextField(blank=True, default='') - order = models.IntegerField(default=0, blank=True) - - class Meta: - ordering = ('order',) - - class MyRelatedUUIDModel(models.Model): - myuuidmodels = models.ManyToManyField(MyUUIDModel) - content = models.TextField(blank=True, default='') -else: - MyUUIDModel = None - MyRelatedUUIDModel = None diff -Nru django-tastypie-0.13.3/tests/core/tests/api.py django-tastypie-0.14.3/tests/core/tests/api.py --- django-tastypie-0.13.3/tests/core/tests/api.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/api.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,217 +0,0 @@ -import json - -from django.contrib.auth.models import User -from django.http import HttpRequest -from django.test import TestCase -from tastypie.api import Api -from tastypie.exceptions import NotRegistered, BadRequest -from tastypie.resources import ModelResource -from tastypie.serializers import Serializer -from core.models import Note -from core.utils import adjust_schema - - -class NoteResource(ModelResource): - class Meta: - resource_name = 'notes' - queryset = Note.objects.filter(is_active=True) - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - - -class ApiTestCase(TestCase): - urls = 'core.tests.api_urls' - - def test_register(self): - # NOTE: these have all been registered in core.tests.api_urls - api = Api() - self.assertEqual(len(api._registry), 0) - - api.register(NoteResource()) - self.assertEqual(len(api._registry), 1) - self.assertEqual(sorted(api._registry.keys()), ['notes']) - - api.register(UserResource()) - self.assertEqual(len(api._registry), 2) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - - api.register(UserResource()) - self.assertEqual(len(api._registry), 2) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - - self.assertEqual(len(api._canonicals), 2) - api.register(UserResource(), canonical=False) - self.assertEqual(len(api._registry), 2) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - self.assertEqual(len(api._canonicals), 2) - - self.assertRaises(ValueError, api.register, NoteResource) - - def test_global_registry(self): - api = Api() - self.assertEqual(len(api._registry), 0) - - api.register(NoteResource()) - self.assertEqual(len(api._registry), 1) - self.assertEqual(sorted(api._registry.keys()), ['notes']) - - api.register(UserResource()) - self.assertEqual(len(api._registry), 2) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - - api.register(UserResource()) - self.assertEqual(len(api._registry), 2) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - - self.assertEqual(len(api._canonicals), 2) - api.register(UserResource(), canonical=False) - self.assertEqual(len(api._registry), 2) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - self.assertEqual(len(api._canonicals), 2) - - def test_unregister(self): - api = Api() - api.register(NoteResource()) - api.register(UserResource(), canonical=False) - self.assertEqual(sorted(api._registry.keys()), ['notes', 'users']) - - self.assertEqual(len(api._canonicals), 1) - api.unregister('users') - self.assertEqual(len(api._registry), 1) - self.assertEqual(sorted(api._registry.keys()), ['notes']) - self.assertEqual(len(api._canonicals), 1) - - api.unregister('notes') - self.assertEqual(len(api._registry), 0) - self.assertEqual(sorted(api._registry.keys()), []) - - api.unregister('users') - self.assertEqual(len(api._registry), 0) - self.assertEqual(sorted(api._registry.keys()), []) - - def test_canonical_resource_for(self): - api = Api() - note_resource = NoteResource() - user_resource = UserResource() - api.register(note_resource) - api.register(user_resource) - self.assertEqual(len(api._canonicals), 2) - - self.assertEqual(isinstance(api.canonical_resource_for('notes'), NoteResource), True) - - api.unregister(user_resource._meta.resource_name) - self.assertRaises(NotRegistered, api.canonical_resource_for, 'users') - - def test_urls(self): - api = Api() - api.register(NoteResource()) - api.register(UserResource()) - - patterns = api.urls - self.assertEqual(len(patterns), 3) - self.assertEqual(sorted([pattern.name for pattern in patterns if hasattr(pattern, 'name')]), ['api_v1_top_level']) - self.assertEqual([[pattern.name for pattern in include.url_patterns if hasattr(pattern, 'name')] for include in patterns if hasattr(include, 'reverse_dict')], [['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail'], ['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail']]) - - api = Api(api_name='v2') - api.register(NoteResource()) - api.register(UserResource()) - - patterns = api.urls - self.assertEqual(len(patterns), 3) - self.assertEqual(sorted([pattern.name for pattern in patterns if hasattr(pattern, 'name')]), ['api_v2_top_level']) - self.assertEqual([[pattern.name for pattern in include.url_patterns if hasattr(pattern, 'name')] for include in patterns if hasattr(include, 'reverse_dict')], [['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail'], ['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail']]) - - def test_top_level(self): - api = Api() - api.register(NoteResource()) - api.register(UserResource()) - request = HttpRequest() - - resp = api.top_level(request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"notes": {"list_endpoint": "/api/v1/notes/", "schema": "/api/v1/notes/schema/"}, "users": {"list_endpoint": "/api/v1/users/", "schema": "/api/v1/users/schema/"}}') - - def test_top_level_include_schema_content(self): - api = Api() - - note_resource = NoteResource() - user_resource = UserResource() - - api.register(note_resource) - api.register(user_resource) - - request = HttpRequest() - request.GET = {'fullschema': 'true'} - - resp = api.top_level(request) - self.assertEqual(resp.status_code, 200) - - content = json.loads(resp.content.decode('utf-8')) - - content['notes']['schema'] = adjust_schema(content['notes']['schema']) - content['users']['schema'] = adjust_schema(content['users']['schema']) - - dummy_request = HttpRequest() - dummy_request.method = 'GET' - - notes_schema = adjust_schema(json.loads(note_resource.get_schema(dummy_request).content.decode('utf-8'))) - user_schema = adjust_schema(json.loads(user_resource.get_schema(dummy_request).content.decode('utf-8'))) - - self.assertEqual(content['notes']['list_endpoint'], '/api/v1/notes/') - self.assertEqual(content['notes']['schema'], notes_schema) - - self.assertEqual(content['users']['list_endpoint'], '/api/v1/users/') - self.assertEqual(content['users']['schema'], user_schema) - - def test_top_level_jsonp(self): - api = Api() - api.register(NoteResource()) - api.register(UserResource()) - request = HttpRequest() - request.META = {'HTTP_ACCEPT': 'text/javascript'} - request.GET = {'callback': 'foo'} - - resp = api.top_level(request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp['content-type'].split(';')[0], 'text/javascript') - self.assertEqual(resp.content.decode('utf-8'), 'foo({"notes": {"list_endpoint": "/api/v1/notes/", "schema": "/api/v1/notes/schema/"}, "users": {"list_endpoint": "/api/v1/users/", "schema": "/api/v1/users/schema/"}})') - - request = HttpRequest() - request.META = {'HTTP_ACCEPT': 'text/javascript'} - request.GET = {'callback': ''} - - # Regression: We expect this, which is fine, but this used to - # be an import error. - with self.assertRaises(BadRequest): - api.top_level(request) - - def test_custom_api_serializer(self): - """Confirm that an Api can use a custom serializer""" - - # Origin: https://github.com/django-tastypie/django-tastypie/pull/817 - - class JSONSerializer(Serializer): - formats = ('json', ) - - api = Api(serializer_class=JSONSerializer) - api.register(NoteResource()) - - request = HttpRequest() - request.META = {'HTTP_ACCEPT': 'text/javascript'} - - resp = api.top_level(request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp['content-type'], 'application/json', - msg="Expected application/json response but received %s" % resp['content-type']) - - request = HttpRequest() - request.META = {'HTTP_ACCEPT': 'application/xml'} - - resp = api.top_level(request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp['content-type'], 'application/json', - msg="Expected application/json response but received %s" % resp['content-type']) diff -Nru django-tastypie-0.13.3/tests/core/tests/api_urls.py django-tastypie-0.14.3/tests/core/tests/api_urls.py --- django-tastypie-0.13.3/tests/core/tests/api_urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/api_urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -from django.conf.urls import include, url - -from core.tests.api import Api, NoteResource, UserResource - - -api = Api() -api.register(NoteResource()) -api.register(UserResource()) - -urlpatterns = [ - url(r'^api/', include(api.urls)), -] diff -Nru django-tastypie-0.13.3/tests/core/tests/authentication.py django-tastypie-0.14.3/tests/core/tests/authentication.py --- django-tastypie-0.13.3/tests/core/tests/authentication.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/authentication.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,653 +0,0 @@ -import base64 -import time -import warnings -from unittest import skipIf - -from django.conf import settings -from django.contrib.auth.models import AnonymousUser, User -from django.http import HttpRequest -from django.test import TestCase - -from tastypie.authentication import Authentication, BasicAuthentication,\ - ApiKeyAuthentication, SessionAuthentication, DigestAuthentication,\ - OAuthAuthentication, MultiAuthentication -from tastypie.http import HttpUnauthorized -from tastypie.models import ApiKey, create_api_key - - -# Be tricky. -from tastypie.authentication import python_digest, oauth2, oauth_provider - -if python_digest is None: - warnings.warn("Running tests without python_digest! Bad news!") -if oauth2 is None: - warnings.warn("Running tests without oauth2! Bad news!") -if oauth_provider is None: - warnings.warn("Running tests without oauth_provider! Bad news!") - - -class AuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_is_authenticated(self): - auth = Authentication() - request = HttpRequest() - # Doesn't matter. Always true. - self.assertTrue(auth.is_authenticated(None)) - self.assertTrue(auth.is_authenticated(request)) - - def test_get_identifier(self): - auth = Authentication() - request = HttpRequest() - self.assertEqual(auth.get_identifier(request), 'noaddr_nohost') - - request = HttpRequest() - request.META['REMOTE_ADDR'] = '127.0.0.1' - request.META['REMOTE_HOST'] = 'nebula.local' - self.assertEqual(auth.get_identifier(request), '127.0.0.1_nebula.local') - - def test_check_active_false(self): - auth = Authentication(require_active=False) - user = User.objects.get(username='johndoe') - self.assertTrue(auth.check_active(user)) - - auth = Authentication(require_active=False) - user = User.objects.get(username='bobdoe') - self.assertTrue(auth.check_active(user)) - - def test_check_active_true(self): - auth = Authentication(require_active=True) - user = User.objects.get(username='johndoe') - self.assertTrue(auth.check_active(user)) - - auth = Authentication(require_active=True) - user = User.objects.get(username='bobdoe') - self.assertFalse(auth.check_active(user)) - - # Check the default. - auth = Authentication() - user = User.objects.get(username='bobdoe') - self.assertFalse(auth.check_active(user)) - - -class BasicAuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_is_authenticated(self): - auth = BasicAuthentication() - request = HttpRequest() - - # No HTTP Basic auth details should fail. - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # HttpUnauthorized with auth type and realm - self.assertEqual(auth.is_authenticated(request)['WWW-Authenticate'], 'Basic Realm="django-tastypie"') - - # Wrong basic auth details. - request.META['HTTP_AUTHORIZATION'] = 'abcdefg' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # No password. - request.META['HTTP_AUTHORIZATION'] = base64.b64encode('daniel'.encode('utf-8')).decode('utf-8') - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong user/password. - request.META['HTTP_AUTHORIZATION'] = base64.b64encode('daniel:pass'.encode('utf-8')).decode('utf-8') - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Correct user/password. - john_doe = User.objects.get(username='johndoe') - john_doe.set_password('pass') - john_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('johndoe:pass'.encode('utf-8')).decode('utf-8') - self.assertEqual(auth.is_authenticated(request), True) - - # Regression: Password with colon. - john_doe = User.objects.get(username='johndoe') - john_doe.set_password('pass:word') - john_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('johndoe:pass:word'.encode('utf-8')).decode('utf-8') - self.assertEqual(auth.is_authenticated(request), True) - - # Capitalization shouldn't matter. - john_doe = User.objects.get(username='johndoe') - john_doe.set_password('pass:word') - john_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'bAsIc %s' % base64.b64encode('johndoe:pass:word'.encode('utf-8')).decode('utf-8') - self.assertEqual(auth.is_authenticated(request), True) - - def test_check_active_true(self): - auth = BasicAuthentication() - request = HttpRequest() - - bob_doe = User.objects.get(username='bobdoe') - bob_doe.set_password('pass') - bob_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('bobdoe:pass'.encode('utf-8')).decode('utf-8') - self.assertFalse(auth.is_authenticated(request)) - - def test_check_active_false(self): - auth = BasicAuthentication(require_active=False) - request = HttpRequest() - - bob_doe = User.objects.get(username='bobdoe') - bob_doe.set_password('pass') - bob_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('bobdoe:pass'.encode('utf-8')).decode('utf-8') - self.assertTrue(auth.is_authenticated(request)) - - -class ApiKeyAuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def setUp(self): - super(ApiKeyAuthenticationTestCase, self).setUp() - ApiKey.objects.all().delete() - - def test_is_authenticated_get_params(self): - auth = ApiKeyAuthentication() - request = HttpRequest() - - # Simulate sending the signal. - john_doe = User.objects.get(username='johndoe') - create_api_key(User, instance=john_doe, created=True) - - # No username/api_key details should fail. - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong username details. - request.GET['username'] = 'foo' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # No api_key. - request.GET['username'] = 'daniel' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong user/api_key. - request.GET['username'] = 'daniel' - request.GET['api_key'] = 'foo' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Correct user/api_key. - john_doe = User.objects.get(username='johndoe') - request.GET['username'] = 'johndoe' - request.GET['api_key'] = john_doe.api_key.key - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), 'johndoe') - - def test_is_authenticated_header(self): - auth = ApiKeyAuthentication() - request = HttpRequest() - - # Simulate sending the signal. - john_doe = User.objects.get(username='johndoe') - create_api_key(User, instance=john_doe, created=True) - - # No username/api_key details should fail. - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong username details. - request.META['HTTP_AUTHORIZATION'] = 'foo' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # No api_key. - request.META['HTTP_AUTHORIZATION'] = 'ApiKey daniel' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong user/api_key. - request.META['HTTP_AUTHORIZATION'] = 'ApiKey daniel:pass' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Correct user/api_key. - john_doe = User.objects.get(username='johndoe') - request.META['HTTP_AUTHORIZATION'] = 'ApiKey johndoe:%s' % john_doe.api_key.key - self.assertEqual(auth.is_authenticated(request), True) - - # Capitalization shouldn't matter. - john_doe = User.objects.get(username='johndoe') - request.META['HTTP_AUTHORIZATION'] = 'aPiKeY johndoe:%s' % john_doe.api_key.key - self.assertEqual(auth.is_authenticated(request), True) - - # No api_key. - john_doe = User.objects.get(username='johndoe') - api_key = john_doe.api_key - api_key.delete() - request.META['HTTP_AUTHORIZATION'] = 'ApiKey johndoe:%s' % api_key.key - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - def test_check_active_true(self): - auth = ApiKeyAuthentication() - request = HttpRequest() - - bob_doe = User.objects.get(username='bobdoe') - create_api_key(User, instance=bob_doe, created=True) - request.META['HTTP_AUTHORIZATION'] = 'ApiKey bobdoe:%s' % bob_doe.api_key.key - self.assertFalse(auth.is_authenticated(request)) - - def test_check_active_false(self): - auth = BasicAuthentication(require_active=False) - request = HttpRequest() - - bob_doe = User.objects.get(username='bobdoe') - create_api_key(User, instance=bob_doe, created=True) - request.META['HTTP_AUTHORIZATION'] = 'ApiKey bobdoe:%s' % bob_doe.api_key.key - self.assertTrue(auth.is_authenticated(request)) - - -class SessionAuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_is_authenticated(self): - auth = SessionAuthentication() - request = HttpRequest() - request.method = 'POST' - request.COOKIES = { - settings.CSRF_COOKIE_NAME: 'abcdef1234567890abcdef1234567890' - } - - # No CSRF token. - request.META = {} - self.assertFalse(auth.is_authenticated(request)) - - # Invalid CSRF token. - request.META = { - 'HTTP_X_CSRFTOKEN': 'abc123' - } - self.assertFalse(auth.is_authenticated(request)) - - # Not logged in. - request.META = { - 'HTTP_X_CSRFTOKEN': 'abcdef1234567890abcdef1234567890' - } - request.user = AnonymousUser() - self.assertFalse(auth.is_authenticated(request)) - - # Logged in. - request.user = User.objects.get(username='johndoe') - self.assertTrue(auth.is_authenticated(request)) - - # Logged in (with GET & no token). - request.method = 'GET' - request.META = {} - request.user = User.objects.get(username='johndoe') - self.assertTrue(auth.is_authenticated(request)) - - # Secure & wrong referrer. - class SecureRequest(HttpRequest): - def _get_scheme(self): - return 'https' - - request = SecureRequest() - request.method = 'POST' - request.COOKIES = { - settings.CSRF_COOKIE_NAME: 'abcdef1234567890abcdef1234567890' - } - request.META = { - 'HTTP_X_CSRFTOKEN': 'abcdef1234567890abcdef1234567890' - } - request.META['HTTP_HOST'] = 'example.com' - request.META['HTTP_REFERER'] = '' - request.user = User.objects.get(username='johndoe') - self.assertFalse(auth.is_authenticated(request)) - - # Secure & correct referrer. - request.META['HTTP_REFERER'] = 'https://example.com/' - self.assertTrue(auth.is_authenticated(request)) - - def test_get_identifier(self): - auth = SessionAuthentication() - request = HttpRequest() - - # Not logged in. - request.user = AnonymousUser() - self.assertEqual(auth.get_identifier(request), '') - - # Logged in. - request.user = User.objects.get(username='johndoe') - self.assertEqual(auth.get_identifier(request), 'johndoe') - - -@skipIf(python_digest is None, "python-digest is not installed") -class DigestAuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def setUp(self): - super(DigestAuthenticationTestCase, self).setUp() - ApiKey.objects.all().delete() - - def test_is_authenticated(self): - auth = DigestAuthentication() - request = HttpRequest() - - # Simulate sending the signal. - john_doe = User.objects.get(username='johndoe') - create_api_key(User, instance=john_doe, created=True) - - # No HTTP Basic auth details should fail. - auth_request = auth.is_authenticated(request) - self.assertEqual(isinstance(auth_request, HttpUnauthorized), True) - - # HttpUnauthorized with auth type and realm - self.assertEqual(auth_request['WWW-Authenticate'].find('Digest'), 0) - self.assertEqual(auth_request['WWW-Authenticate'].find(' realm="django-tastypie"') > 0, True) - self.assertEqual(auth_request['WWW-Authenticate'].find(' opaque=') > 0, True) - self.assertEqual(auth_request['WWW-Authenticate'].find('nonce=') > 0, True) - - # Wrong basic auth details. - request.META['HTTP_AUTHORIZATION'] = 'abcdefg' - auth_request = auth.is_authenticated(request) - self.assertEqual(isinstance(auth_request, HttpUnauthorized), True) - - # No password. - request.META['HTTP_AUTHORIZATION'] = base64.b64encode('daniel'.encode('utf-8')).decode('utf-8') - auth_request = auth.is_authenticated(request) - self.assertEqual(isinstance(auth_request, HttpUnauthorized), True) - - # Wrong user/password. - request.META['HTTP_AUTHORIZATION'] = base64.b64encode('daniel:pass'.encode('utf-8')).decode('utf-8') - auth_request = auth.is_authenticated(request) - self.assertEqual(isinstance(auth_request, HttpUnauthorized), True) - - # Correct user/password. - john_doe = User.objects.get(username='johndoe') - request.META['HTTP_AUTHORIZATION'] = python_digest.build_authorization_request( - username=john_doe.username, - method=request.method, - uri='/', - nonce_count=1, - digest_challenge=python_digest.parse_digest_challenge(auth_request['WWW-Authenticate']), - password=john_doe.api_key.key - ) - auth_request = auth.is_authenticated(request) - self.assertEqual(auth_request, True) - - def test_check_active_true(self): - auth = DigestAuthentication() - request = HttpRequest() - - bob_doe = User.objects.get(username='bobdoe') - create_api_key(User, instance=bob_doe, created=True) - auth_request = auth.is_authenticated(request) - request.META['HTTP_AUTHORIZATION'] = python_digest.build_authorization_request( - username=bob_doe.username, - method=request.method, - uri='/', - nonce_count=1, - digest_challenge=python_digest.parse_digest_challenge(auth_request['WWW-Authenticate']), - password=bob_doe.api_key.key - ) - auth_request = auth.is_authenticated(request) - self.assertFalse(auth_request) - - def test_check_active_false(self): - auth = DigestAuthentication(require_active=False) - request = HttpRequest() - - bob_doe = User.objects.get(username='bobdoe') - create_api_key(User, instance=bob_doe, created=True) - auth_request = auth.is_authenticated(request) - request.META['HTTP_AUTHORIZATION'] = python_digest.build_authorization_request( - username=bob_doe.username, - method=request.method, - uri='/', - nonce_count=1, - digest_challenge=python_digest.parse_digest_challenge(auth_request['WWW-Authenticate']), - password=bob_doe.api_key.key - ) - auth_request = auth.is_authenticated(request) - self.assertTrue(auth_request, True) - - -@skipIf(not oauth2 or not oauth_provider, "oauth provider not installed") -class OAuthAuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def setUp(self): - super(OAuthAuthenticationTestCase, self).setUp() - - self.request = HttpRequest() - self.request.META['SERVER_NAME'] = 'testsuite' - self.request.META['SERVER_PORT'] = '8080' - self.request.REQUEST = self.request.GET = {} - self.request.method = "GET" - - from oauth_provider.models import Consumer, Token, Resource - self.user = User.objects.create_user('daniel', 'test@example.com', 'password') - self.user_inactive = User.objects.get(username='bobdoe') - self.resource, _ = Resource.objects.get_or_create(url='test', defaults={ - 'name': 'Test Resource' - }) - self.consumer, _ = Consumer.objects.get_or_create(key='123', defaults={ - 'name': 'Test', - 'description': 'Testing...' - }) - self.token, _ = Token.objects.get_or_create(key='foo', token_type=Token.ACCESS, defaults={ - 'consumer': self.consumer, - 'resource': self.resource, - 'secret': '', - 'user': self.user, - }) - self.token_inactive, _ = Token.objects.get_or_create(key='bar', token_type=Token.ACCESS, defaults={ - 'consumer': self.consumer, - 'resource': self.resource, - 'secret': '', - 'user': self.user_inactive, - }) - - def test_is_authenticated(self): - auth = OAuthAuthentication() - - # Invalid request. - resp = auth.is_authenticated(self.request) - self.assertEqual(resp.status_code, 401) - - # No username/api_key details should fail. - self.request.REQUEST = self.request.GET = { - 'oauth_consumer_key': '123', - 'oauth_nonce': 'abc', - 'oauth_signature': '&', - 'oauth_signature_method': 'PLAINTEXT', - 'oauth_timestamp': str(int(time.time())), - 'oauth_token': 'foo', - } - self.request.META['Authorization'] = 'OAuth ' + ','.join( - [key + '=' + value for key, value in self.request.REQUEST.items()]) - resp = auth.is_authenticated(self.request) - self.assertEqual(resp, True) - self.assertEqual(self.request.user.pk, self.user.pk) - - def test_check_active_true(self): - auth = OAuthAuthentication() - - # No username/api_key details should fail. - self.request.REQUEST = self.request.GET = { - 'oauth_consumer_key': '123', - 'oauth_nonce': 'abc', - 'oauth_signature': '&', - 'oauth_signature_method': 'PLAINTEXT', - 'oauth_timestamp': str(int(time.time())), - 'oauth_token': 'bar', - } - self.request.META['Authorization'] = 'OAuth ' + ','.join( - [key + '=' + value for key, value in self.request.REQUEST.items()]) - resp = auth.is_authenticated(self.request) - self.assertFalse(resp) - - def test_check_active_false(self): - auth = OAuthAuthentication(require_active=False) - - # No username/api_key details should fail. - self.request.REQUEST = self.request.GET = { - 'oauth_consumer_key': '123', - 'oauth_nonce': 'abc', - 'oauth_signature': '&', - 'oauth_signature_method': 'PLAINTEXT', - 'oauth_timestamp': str(int(time.time())), - 'oauth_token': 'bar', - } - self.request.META['Authorization'] = 'OAuth ' + ','.join( - [key + '=' + value for key, value in self.request.REQUEST.items()]) - resp = auth.is_authenticated(self.request) - self.assertTrue(resp) - self.assertEqual(self.request.user.pk, self.user_inactive.pk) - - -class MultiAuthenticationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_apikey_and_authentication_enforce_user(self): - session_auth = SessionAuthentication() - api_key_auth = ApiKeyAuthentication() - auth = MultiAuthentication(api_key_auth, session_auth) - john_doe = User.objects.get(username='johndoe') - request1 = HttpRequest() - request2 = HttpRequest() - request3 = HttpRequest() - - request1.method = 'POST' - request1.META = { - 'HTTP_X_CSRFTOKEN': 'abcdef1234567890abcdef1234567890' - } - request1.COOKIES = { - settings.CSRF_COOKIE_NAME: 'abcdef1234567890abcdef1234567890' - } - request1.user = john_doe - - request2.POST['username'] = 'janedoe' - request2.POST['api_key'] = 'invalid key' - - request3.method = 'POST' - request3.META = { - 'HTTP_X_CSRFTOKEN': 'abcdef1234567890abcdef1234567890' - } - request3.COOKIES = { - settings.CSRF_COOKIE_NAME: 'abcdef1234567890abcdef1234567890' - } - request3.user = john_doe - request3.POST['username'] = 'janedoe' - request3.POST['api_key'] = 'invalid key' - - # session auth should pass if since john_doe is logged in - self.assertTrue(session_auth.is_authenticated(request1)) - # api key auth should fail because of invalid api key - self.assertEqual(isinstance(api_key_auth.is_authenticated(request2), HttpUnauthorized), True) - - # multi auth shouldn't change users if api key auth fails - # multi auth passes since session auth is valid - self.assertEqual(request3.user.username, 'johndoe') - self.assertTrue(auth.is_authenticated(request3)) - self.assertEqual(request3.user.username, 'johndoe') - - def test_apikey_and_authentication(self): - auth = MultiAuthentication(ApiKeyAuthentication(), Authentication()) - request = HttpRequest() - - john_doe = User.objects.get(username='johndoe') - - # No username/api_key details should pass. - self.assertEqual(auth.is_authenticated(request), True) - - # The identifier should be the basic auth stock. - self.assertEqual(auth.get_identifier(request), 'noaddr_nohost') - - # Wrong username details. - request = HttpRequest() - request.GET['username'] = 'foo' - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), 'noaddr_nohost') - - # No api_key. - request = HttpRequest() - request.GET['username'] = 'daniel' - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), 'noaddr_nohost') - - # Wrong user/api_key. - request = HttpRequest() - request.GET['username'] = 'daniel' - request.GET['api_key'] = 'foo' - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), 'noaddr_nohost') - - request = HttpRequest() - request.GET['username'] = 'johndoe' - request.GET['api_key'] = john_doe.api_key.key - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.username) - - def test_multiauth_apikey_and_basic_auth__no_details_fails(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - def test_multiauth_apikey_and_basic_auth__basic_returns_authenticate(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - - self.assertEqual( - auth.is_authenticated(request)['WWW-Authenticate'], - 'Basic Realm="django-tastypie"' - ) - - def test_multiauth_apikey_and_basic_auth__api_key_works_in_query(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - john_doe = User.objects.get(username='johndoe') - - request.GET['username'] = john_doe.username - request.GET['api_key'] = john_doe.api_key.key - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.username) - - def test_multiauth_apikey_and_basic_auth__api_key_works_in_header(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - john_doe = User.objects.get(username='johndoe') - - request.META['HTTP_AUTHORIZATION'] = 'ApiKey %s:%s' % (john_doe.username, john_doe.api_key.key,) - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.username) - - def test_multiauth_apikey_and_basic_auth__api_key_works_in_header__space_in_username(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - john_doe = User.objects.get(username='johndoe') - john_doe.username = 'john doe' - john_doe.save() - - request.META['HTTP_AUTHORIZATION'] = 'ApiKey %s:%s' % (john_doe.username, john_doe.api_key.key,) - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.username) - - def test_multiauth_apikey_and_basic_auth__basic_auth_works(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - john_doe = User.objects.get(username='johndoe') - john_doe.set_password('pass') - john_doe.save() - - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('johndoe:pass'.encode('utf-8')).decode('utf-8') - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.username) - - def test_multiauth_apikey_and_basic_auth__basic_auth_works__space_in_username(self): - auth = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication()) - request = HttpRequest() - john_doe = User.objects.get(username='johndoe') - john_doe.username = 'john doe' - john_doe.set_password('pass') - john_doe.save() - - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('john doe:pass'.encode('utf-8')).decode('utf-8') - - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.username) diff -Nru django-tastypie-0.13.3/tests/core/tests/authorization.py django-tastypie-0.14.3/tests/core/tests/authorization.py --- django-tastypie-0.13.3/tests/core/tests/authorization.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/authorization.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,240 +0,0 @@ -from django.test import TestCase -from django.http import HttpRequest -from django.contrib.auth.models import User, Permission -from core.models import Note -from tastypie.authorization import Authorization, ReadOnlyAuthorization, DjangoAuthorization -from tastypie.exceptions import Unauthorized -from tastypie import fields -from tastypie.resources import Resource, ModelResource - - -class NoRulesNoteResource(ModelResource): - class Meta: - resource_name = 'notes' - queryset = Note.objects.filter(is_active=True) - authorization = Authorization() - - -class ReadOnlyNoteResource(ModelResource): - class Meta: - resource_name = 'notes' - queryset = Note.objects.filter(is_active=True) - authorization = ReadOnlyAuthorization() - - -class DjangoNoteResource(ModelResource): - class Meta: - resource_name = 'notes' - queryset = Note.objects.filter(is_active=True) - authorization = DjangoAuthorization() - - -class NotAModel(object): - name = 'Foo' - - -class NotAModelResource(Resource): - name = fields.CharField(attribute='name') - - class Meta: - resource_name = 'notamodel' - object_class = NotAModel - authorization = DjangoAuthorization() - - -class AuthorizationTestCase(TestCase): - fixtures = ['note_testdata'] - - def test_no_rules(self): - request = HttpRequest() - resource = NoRulesNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.read_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'POST' - self.assertRaises(NotImplementedError, auth.create_list, resource.get_object_list(bundle.request), bundle) - self.assertTrue(auth.create_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.update_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.delete_detail(resource.get_object_list(bundle.request)[0], bundle)) - - def test_read_only(self): - request = HttpRequest() - resource = ReadOnlyNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.read_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'POST' - self.assertEqual(len(auth.create_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.create_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.update_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.delete_detail, resource.get_object_list(bundle.request)[0], bundle) - - -class DjangoAuthorizationTestCase(TestCase): - fixtures = ['note_testdata'] - - def setUp(self): - super(DjangoAuthorizationTestCase, self).setUp() - self.add = Permission.objects.get_by_natural_key('add_note', 'core', 'note') - self.change = Permission.objects.get_by_natural_key('change_note', 'core', 'note') - self.delete = Permission.objects.get_by_natural_key('delete_note', 'core', 'note') - self.user = User.objects.all()[0] - self.user.user_permissions.clear() - - def test_no_perms(self): - # sanity check: user has no permissions - self.assertFalse(self.user.get_all_permissions()) - - request = HttpRequest() - request.user = self.user - # with no permissions, api is read-only - resource = DjangoNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.read_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'POST' - self.assertEqual(len(auth.create_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.create_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.update_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.delete_detail, resource.get_object_list(bundle.request)[0], bundle) - - def test_add_perm(self): - request = HttpRequest() - request.user = self.user - - # give add permission - request.user.user_permissions.add(self.add) - - request = HttpRequest() - request.user = self.user - resource = DjangoNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.read_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'POST' - self.assertEqual(len(auth.create_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.create_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.update_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.delete_detail, resource.get_object_list(bundle.request)[0], bundle) - - def test_change_perm(self): - request = HttpRequest() - request.user = self.user - - # give change permission - request.user.user_permissions.add(self.change) - - resource = DjangoNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.read_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'POST' - self.assertEqual(len(auth.create_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.create_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.update_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.delete_detail, resource.get_object_list(bundle.request)[0], bundle) - - def test_delete_perm(self): - request = HttpRequest() - request.user = self.user - - # give delete permission - request.user.user_permissions.add(self.delete) - - resource = DjangoNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.read_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'POST' - self.assertEqual(len(auth.create_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.create_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 0) - self.assertRaises(Unauthorized, auth.update_detail, resource.get_object_list(bundle.request)[0], bundle) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.delete_detail(resource.get_object_list(bundle.request)[0], bundle)) - - def test_all(self): - request = HttpRequest() - request.user = self.user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - resource = DjangoNoteResource() - auth = resource._meta.authorization - bundle = resource.build_bundle(request=request) - - bundle.request.method = 'GET' - self.assertEqual(len(auth.read_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.read_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'POST' - self.assertEqual(len(auth.create_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.create_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'PUT' - self.assertEqual(len(auth.update_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.update_detail(resource.get_object_list(bundle.request)[0], bundle)) - - bundle.request.method = 'DELETE' - self.assertEqual(len(auth.delete_list(resource.get_object_list(bundle.request), bundle)), 4) - self.assertTrue(auth.delete_detail(resource.get_object_list(bundle.request)[0], bundle)) diff -Nru django-tastypie-0.13.3/tests/core/tests/cache.py django-tastypie-0.14.3/tests/core/tests/cache.py --- django-tastypie-0.13.3/tests/core/tests/cache.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/cache.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -import mock - -from django.core.cache import cache -from django.test import TestCase - -from tastypie.cache import NoCache, SimpleCache - - -class NoCacheTestCase(TestCase): - def tearDown(self): - cache.delete('foo') - cache.delete('moof') - super(NoCacheTestCase, self).tearDown() - - def test_get(self): - cache.set('foo', 'bar', 60) - cache.set('moof', 'baz', 1) - - no_cache = NoCache() - self.assertEqual(no_cache.get('foo'), None) - self.assertEqual(no_cache.get('moof'), None) - self.assertEqual(no_cache.get(''), None) - - def test_set(self): - no_cache = NoCache() - no_cache.set('foo', 'bar') - no_cache.set('moof', 'baz', timeout=1) - - # Use the underlying cache system to verify. - self.assertEqual(cache.get('foo'), None) - self.assertEqual(cache.get('moof'), None) - - -class SimpleCacheTestCase(TestCase): - def tearDown(self): - cache.delete('foo') - cache.delete('moof') - super(SimpleCacheTestCase, self).tearDown() - - def test_get(self): - cache.set('foo', 'bar', 60) - cache.set('moof', 'baz', 1) - - simple_cache = SimpleCache() - self.assertEqual(simple_cache.get('foo'), 'bar') - self.assertEqual(simple_cache.get('moof'), 'baz') - self.assertEqual(simple_cache.get(''), None) - - def test_set(self): - simple_cache = SimpleCache(timeout=1) - - with mock.patch.object(simple_cache, 'cache', mock.Mock(wraps=simple_cache.cache)) as mocked_cache: - simple_cache.set('foo', 'bar', timeout=10) - simple_cache.set('moof', 'baz') - - # Use the underlying cache system to verify. - self.assertEqual(cache.get('foo'), 'bar') - self.assertEqual(cache.get('moof'), 'baz') - - # make sure cache was called with correct timeouts. - self.assertEqual(mocked_cache.set.call_args_list[0][0][2], 10) - self.assertEqual(mocked_cache.set.call_args_list[1][0][2], 1) diff -Nru django-tastypie-0.13.3/tests/core/tests/commands.py django-tastypie-0.14.3/tests/core/tests/commands.py --- django-tastypie-0.13.3/tests/core/tests/commands.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/commands.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -from django.core.management import call_command -from django.db import models -from django.test import TestCase - -from tastypie.compat import get_user_model -from tastypie.models import ApiKey, create_api_key - - -class BackfillApiKeysTestCase(TestCase): - def setUp(self): - super(BackfillApiKeysTestCase, self).setUp() - self.User = get_user_model() - - # Disconnect the signal to prevent automatic key generation. - models.signals.post_save.disconnect(create_api_key, sender=self.User) - - def tearDown(self): - # Reconnect the signal. - models.signals.post_save.connect(create_api_key, sender=self.User) - super(BackfillApiKeysTestCase, self).tearDown() - - def test_command(self): - self.assertEqual(ApiKey.objects.count(), 0) - - # Create a new User that ought not to have an API key. - new_user = self.User.objects.create_user(username='mr_pants', password='password', email='mister@pants.com') - - self.assertEqual(ApiKey.objects.count(), 0) - - call_command('backfill_api_keys', verbosity=0) - - self.assertEqual(ApiKey.objects.count(), 1) - - self.assertEqual(ApiKey.objects.filter(user=new_user).count(), 1) diff -Nru django-tastypie-0.13.3/tests/core/tests/fields.py django-tastypie-0.14.3/tests/core/tests/fields.py --- django-tastypie-0.13.3/tests/core/tests/fields.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/fields.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1359 +0,0 @@ -import datetime -from dateutil.tz import tzoffset -from decimal import Decimal - -from django.db import models -from django.contrib.auth.models import User -from django.test import TestCase -from django.http import HttpRequest - -from tastypie.bundle import Bundle -from tastypie.exceptions import ApiFieldError, NotFound -from tastypie.fields import NOT_PROVIDED, ApiField, BooleanField, CharField,\ - DateField, DateTimeField, DecimalField, DictField, FileField, FloatField,\ - IntegerField, ListField, TimeField, ToOneField, ToManyField -from tastypie.resources import ALL, ModelResource -from tastypie.utils import aware_datetime - -from core.models import Note, Subject, MediaBit -from core.tests.mocks import MockRequest - - -class ApiFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = ApiField() - self.assertEqual(field_1.instance_name, None) - self.assertEqual(field_1.attribute, None) - self.assertEqual(field_1._default, NOT_PROVIDED) - self.assertEqual(field_1.null, False) - self.assertEqual(field_1.help_text, '') - self.assertEqual(field_1.use_in, 'all') - - field_2 = ApiField(attribute='foo', default=True, null=True, readonly=True, help_text='Foo.', use_in="foo") - self.assertEqual(field_2.instance_name, None) - self.assertEqual(field_2.attribute, 'foo') - self.assertEqual(field_2._default, True) - self.assertEqual(field_2.null, True) - self.assertEqual(field_2.readonly, True) - self.assertEqual(field_2.help_text, 'Foo.') - self.assertEqual(field_1.use_in, 'all') - - field_3 = ApiField(use_in="list") - self.assertEqual(field_3.use_in, 'list') - - field_4 = ApiField(use_in="detail") - self.assertEqual(field_4.use_in, 'detail') - - def use_in_callable(x): - return True - field_5 = ApiField(use_in=use_in_callable) - self.assertTrue(field_5.use_in is use_in_callable) - - def test_dehydrated_type(self): - field_1 = ApiField() - self.assertEqual(field_1.dehydrated_type, 'string') - - def test_has_default(self): - field_1 = ApiField() - self.assertEqual(field_1.has_default(), False) - - field_2 = ApiField(default=True) - self.assertEqual(field_2.has_default(), True) - - def test_default(self): - field_1 = ApiField() - self.assertEqual(isinstance(field_1.default, NOT_PROVIDED), True) - - field_2 = ApiField(default=True) - self.assertEqual(field_2.default, True) - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - # With no attribute or default, we should get ``None``. - field_1 = ApiField() - self.assertEqual(field_1.dehydrate(bundle), None) - - # Still no attribute, so we should pick up the default - field_2 = ApiField(default=True) - self.assertEqual(field_2.dehydrate(bundle), True) - - # Wrong attribute should yield default. - field_3 = ApiField(attribute='foo', default=True) - self.assertEqual(field_3.dehydrate(bundle), True) - - # Wrong attribute should yield null. - field_4 = ApiField(attribute='foo', null=True) - self.assertEqual(field_4.dehydrate(bundle), None) - - # Correct attribute. - field_5 = ApiField(attribute='title', default=True) - self.assertEqual(field_5.dehydrate(bundle), u'First Post!') - - # Correct callable attribute. - field_6 = ApiField(attribute='what_time_is_it', default=True) - self.assertEqual(field_6.dehydrate(bundle), aware_datetime(2010, 4, 1, 0, 48)) - - def test_convert(self): - field_1 = ApiField() - self.assertEqual(field_1.convert('foo'), 'foo') - self.assertEqual(field_1.convert(True), True) - - def test_hydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - # With no value, default or nullable, we should get an ``ApiFieldError``. - field_1 = ApiField() - field_1.instance_name = 'api' - self.assertRaises(ApiFieldError, field_1.hydrate, bundle) - - # The default. - field_2 = ApiField(default='foo') - field_2.instance_name = 'api' - self.assertEqual(field_2.hydrate(bundle), 'foo') - - # The callable default. - def foo(): - return 'bar' - - field_3 = ApiField(default=foo) - field_3.instance_name = 'api' - self.assertEqual(field_3.hydrate(bundle), 'bar') - - # The nullable case. - field_4 = ApiField(null=True) - field_4.instance_name = 'api' - self.assertEqual(field_4.hydrate(bundle), None) - - # The readonly case. - field_5 = ApiField(readonly=True) - field_5.instance_name = 'api' - bundle.data['api'] = 'abcdef' - self.assertEqual(field_5.hydrate(bundle), None) - - # A real, live attribute! - field_6 = ApiField(attribute='title') - field_6.instance_name = 'api' - bundle.data['api'] = note.title - self.assertEqual(field_6.hydrate(bundle), u'First Post!') - - # Make sure it uses attribute when there's no data - field_7 = ApiField(attribute='title') - field_7.instance_name = 'notinbundle' - self.assertEqual(field_7.hydrate(bundle), u'First Post!') - - # Make sure it falls back to instance name if there is no attribute - field_8 = ApiField() - field_8.instance_name = 'title' - self.assertEqual(field_8.hydrate(bundle), u'First Post!') - - # Attribute & null regression test. - # First, simulate data missing from the bundle & ``null=True``. - field_9 = ApiField(attribute='notinbundle', null=True) - field_9.instance_name = 'notinbundle' - self.assertEqual(field_9.hydrate(bundle), None) - # The do something in the bundle also with ``null=True``. - field_10 = ApiField(attribute='title', null=True) - field_10.instance_name = 'title' - self.assertEqual(field_10.hydrate(bundle), u'First Post!') - - # The blank case. - field_11 = ApiField(attribute='notinbundle', blank=True) - field_11.instance_name = 'notinbundle' - self.assertEqual(field_11.hydrate(bundle), None) - - bundle.data['title'] = note.title - field_12 = ApiField(attribute='title', blank=True) - field_12.instance_name = 'title' - self.assertEqual(field_12.hydrate(bundle), u'First Post!') - - -class CharFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = CharField() - self.assertEqual(field_1.help_text, 'Unicode string data. Ex: "Hello World"') - - field_2 = CharField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = CharField() - self.assertEqual(field_1.dehydrated_type, 'string') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = CharField(attribute='title', default=True) - self.assertEqual(field_1.dehydrate(bundle), u'First Post!') - - field_2 = CharField(default=20) - self.assertEqual(field_2.dehydrate(bundle), u'20') - - -class FileFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = FileField() - self.assertEqual(field_1.help_text, 'A file URL as a string. Ex: "http://media.example.com/media/photos/my_photo.jpg"') - - field_2 = FileField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = FileField() - self.assertEqual(field_1.dehydrated_type, 'string') - - def test_dehydrate(self): - bit = MediaBit.objects.get(pk=1) - bundle = Bundle(obj=bit) - - field_1 = FileField(attribute='image', default=True) - self.assertEqual(field_1.dehydrate(bundle), u'http://localhost:8080/media/lulz/catz.gif') - - field_2 = FileField(default='http://media.example.com/img/default_avatar.jpg') - self.assertEqual(field_2.dehydrate(bundle), u'http://media.example.com/img/default_avatar.jpg') - - bit = MediaBit.objects.get(pk=1) - bit.image = '' - bundle = Bundle(obj=bit) - - field_3 = FileField(attribute='image', default=True) - self.assertEqual(field_3.dehydrate(bundle), None) - - bit.image = None - bundle = Bundle(obj=bit) - - field_4 = FileField(attribute='image', null=True) - self.assertEqual(field_4.dehydrate(bundle), None) - - -class IntegerFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = IntegerField() - self.assertEqual(field_1.help_text, 'Integer data. Ex: 2673') - - field_2 = IntegerField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = IntegerField() - self.assertEqual(field_1.dehydrated_type, 'integer') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = IntegerField(default=25) - self.assertEqual(field_1.dehydrate(bundle), 25) - - field_2 = IntegerField(default='20') - self.assertEqual(field_2.dehydrate(bundle), 20) - - field_3 = IntegerField(default=18.5) - self.assertEqual(field_3.dehydrate(bundle), 18) - - -class FloatFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = FloatField() - self.assertEqual(field_1.help_text, 'Floating point numeric data. Ex: 26.73') - - field_2 = FloatField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = FloatField() - self.assertEqual(field_1.dehydrated_type, 'float') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = FloatField(default=20) - self.assertEqual(field_1.dehydrate(bundle), 20.0) - - field_2 = IntegerField(default=18.5) - self.assertEqual(field_2.dehydrate(bundle), 18) - - -class DecimalFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = DecimalField() - self.assertEqual(field_1.help_text, 'Fixed precision numeric data. Ex: 26.73') - - field_2 = DecimalField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = DecimalField() - self.assertEqual(field_1.dehydrated_type, 'decimal') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = DecimalField(default='20') - self.assertEqual(field_1.dehydrate(bundle), Decimal('20.0')) - - field_2 = DecimalField(default='18.5') - self.assertEqual(field_2.dehydrate(bundle), Decimal('18.5')) - - field_3 = DecimalField(default=21.5) - self.assertEqual(field_3.dehydrate(bundle), Decimal('21.5')) - - def test_hydrate(self): - bundle = Bundle(data={ - 'decimal-y': '18.50', - }) - - field_1 = DecimalField(default='20') - self.assertEqual(field_1.hydrate(bundle), Decimal('20.0')) - - field_2 = DecimalField(default='18.5') - self.assertEqual(field_2.hydrate(bundle), Decimal('18.5')) - - bundle = Bundle(data={'foo': '1.5'}) - field_3 = DecimalField() - field_3.instance_name = 'foo' - self.assertEqual(field_3.hydrate(bundle), Decimal('1.5')) - - bundle = Bundle(data={'foo': 'xxx'}) - field_4 = DecimalField(attribute='foo') - field_4.instance_name = 'foo' - self.assertRaises(ApiFieldError, field_4.hydrate, bundle) - - def test_model_resource_correct_association(self): - api_field = ModelResource.api_field_from_django_field(models.DecimalField()) - self.assertEqual(api_field, DecimalField) - - -class ListFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = ListField() - self.assertEqual(field_1.help_text, "A list of data. Ex: ['abc', 26.73, 8]") - - field_2 = ListField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = ListField() - self.assertEqual(field_1.dehydrated_type, 'list') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = ListField(default=[1, 2, 3]) - self.assertEqual(field_1.dehydrate(bundle), [1, 2, 3]) - - field_2 = ListField(default=['abc']) - self.assertEqual(field_2.dehydrate(bundle), ['abc']) - - -class DictFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = DictField() - self.assertEqual(field_1.help_text, "A dictionary of data. Ex: {'price': 26.73, 'name': 'Daniel'}") - - field_2 = DictField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = DictField() - self.assertEqual(field_1.dehydrated_type, 'dict') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = DictField(default={'price': 12.34, 'name': 'Daniel'}) - self.assertEqual(field_1.dehydrate(bundle), {'price': 12.34, 'name': 'Daniel'}) - - field_2 = DictField(default={'name': 'Daniel'}) - self.assertEqual(field_2.dehydrate(bundle), {'name': 'Daniel'}) - - -class BooleanFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = BooleanField() - self.assertEqual(field_1.help_text, 'Boolean data. Ex: True') - - field_2 = BooleanField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = BooleanField() - self.assertEqual(field_1.dehydrated_type, 'boolean') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = BooleanField(attribute='is_active', default=False) - self.assertEqual(field_1.dehydrate(bundle), True) - - field_2 = BooleanField(default=True) - self.assertEqual(field_2.dehydrate(bundle), True) - - -class TimeFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = TimeField() - self.assertEqual(field_1.help_text, 'A time as string. Ex: "20:05:23"') - field_2 = TimeField(help_text='Custom.') - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = TimeField() - self.assertEqual(field_1.dehydrated_type, 'time') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = TimeField(attribute='created') - self.assertEqual(field_1.dehydrate(bundle), aware_datetime(2010, 3, 30, 20, 5)) - - field_2 = TimeField(default=datetime.time(23, 5, 58)) - self.assertEqual(field_2.dehydrate(bundle), datetime.time(23, 5, 58)) - - field_3 = TimeField(attribute='created_string') - - note.created_string = '13:06:00' - self.assertEqual(field_3.dehydrate(bundle), datetime.time(13, 6)) - - note.created_string = '13:37:44' - self.assertEqual(field_3.dehydrate(bundle), datetime.time(13, 37, 44)) - - note.created_string = 'hello' - self.assertRaises(ApiFieldError, field_3.dehydrate, bundle) - - def test_hydrate(self): - bundle_1 = Bundle(data={'time': '03:49'}) - field_1 = TimeField(attribute='created') - field_1.instance_name = 'time' - self.assertEqual(field_1.hydrate(bundle_1), datetime.time(3, 49)) - - bundle_2 = Bundle() - field_2 = TimeField(default=datetime.time(17, 40)) - field_2.instance_name = 'doesnotmatter' # Wont find in bundle data - self.assertEqual(field_2.hydrate(bundle_2), datetime.time(17, 40)) - - bundle_3 = Bundle(data={'time': '22:08:11'}) - field_3 = TimeField(attribute='created_string') - field_3.instance_name = 'time' - self.assertEqual(field_3.hydrate(bundle_3), datetime.time(22, 8, 11)) - - bundle_4 = Bundle(data={'time': '07:45'}) - field_4 = TimeField(attribute='created') - field_4.instance_name = 'time' - self.assertEqual(field_4.hydrate(bundle_4), datetime.time(7, 45)) - - bundle_5 = Bundle(data={'time': None}) - field_5 = TimeField(attribute='created', null=True) - field_5.instance_name = 'time' - self.assertEqual(field_5.hydrate(bundle_5), None) - - -class DateFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = CharField() - self.assertEqual(field_1.help_text, 'Unicode string data. Ex: "Hello World"') - - field_2 = CharField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = DateField() - self.assertEqual(field_1.dehydrated_type, 'date') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = DateField(attribute='created') - self.assertEqual(field_1.dehydrate(bundle), aware_datetime(2010, 3, 30, 20, 5)) - - field_2 = DateField(default=datetime.date(2010, 4, 1)) - self.assertEqual(field_2.dehydrate(bundle), datetime.date(2010, 4, 1)) - - note.created_string = '2010-04-02' - field_3 = DateField(attribute='created_string') - self.assertEqual(field_3.dehydrate(bundle), datetime.date(2010, 4, 2)) - - def test_hydrate(self): - bundle_1 = Bundle(data={ - 'date': '2010-05-12', - }) - field_1 = DateField(attribute='created') - field_1.instance_name = 'date' - self.assertEqual(field_1.hydrate(bundle_1), datetime.date(2010, 5, 12)) - - bundle_2 = Bundle() - field_2 = DateField(default=datetime.date(2010, 4, 1)) - field_2.instance_name = 'date' - self.assertEqual(field_2.hydrate(bundle_2), datetime.date(2010, 4, 1)) - - bundle_3 = Bundle(data={ - 'date': 'Wednesday, May 12, 2010', - }) - field_3 = DateField(attribute='created_string') - field_3.instance_name = 'date' - self.assertEqual(field_3.hydrate(bundle_3), datetime.date(2010, 5, 12)) - - bundle_4 = Bundle(data={ - 'date': '5 Apr 2010', - }) - field_4 = DateField(attribute='created') - field_4.instance_name = 'date' - self.assertEqual(field_4.hydrate(bundle_4), datetime.date(2010, 4, 5)) - - bundle_5 = Bundle(data={ - 'date': None, - }) - field_5 = DateField(attribute='created', null=True) - field_5.instance_name = 'date' - self.assertEqual(field_5.hydrate(bundle_5), None) - - def test_model_resource_correct_association(self): - api_field = ModelResource.api_field_from_django_field(models.DateField()) - self.assertEqual(api_field, DateField) - - -class DateTimeFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = CharField() - self.assertEqual(field_1.help_text, 'Unicode string data. Ex: "Hello World"') - - field_2 = CharField(help_text="Custom.") - self.assertEqual(field_2.help_text, 'Custom.') - - def test_dehydrated_type(self): - field_1 = DateTimeField() - self.assertEqual(field_1.dehydrated_type, 'datetime') - - def test_dehydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = DateTimeField(attribute='created') - self.assertEqual(field_1.dehydrate(bundle), aware_datetime(2010, 3, 30, 20, 5)) - - field_2 = DateTimeField(default=aware_datetime(2010, 4, 1, 1, 7)) - self.assertEqual(field_2.dehydrate(bundle), aware_datetime(2010, 4, 1, 1, 7)) - - note.created_string = '2010-04-02 01:11:00' - field_3 = DateTimeField(attribute='created_string') - self.assertEqual(field_3.dehydrate(bundle), aware_datetime(2010, 4, 2, 1, 11)) - - def test_hydrate(self): - bundle_1 = Bundle(data={ - 'datetime': '2010-05-12 10:36:28', - }) - field_1 = DateTimeField(attribute='created') - field_1.instance_name = 'datetime' - self.assertEqual(field_1.hydrate(bundle_1), aware_datetime(2010, 5, 12, 10, 36, 28)) - - bundle_2 = Bundle() - field_2 = DateTimeField(default=aware_datetime(2010, 4, 1, 2, 0)) - field_2.instance_name = 'datetime' - self.assertEqual(field_2.hydrate(bundle_2), aware_datetime(2010, 4, 1, 2, 0)) - - bundle_3 = Bundle(data={ - 'datetime': 'Tue, 30 Mar 2010 20:05:00 -0500', - }) - field_3 = DateTimeField(attribute='created_string') - field_3.instance_name = 'datetime' - self.assertEqual(field_3.hydrate(bundle_3), aware_datetime(2010, 3, 30, 20, 5, tzinfo=tzoffset(None, -18000))) - - bundle_4 = Bundle(data={ - 'datetime': None, - }) - field_4 = DateField(attribute='created', null=True) - field_4.instance_name = 'datetime' - self.assertEqual(field_4.hydrate(bundle_4), None) - - bundle_5 = Bundle(data={'datetime': 'foo'}) - field_5 = DateTimeField() - field_5.instance_name = 'datetime' - self.assertRaises(ApiFieldError, field_5.hydrate, bundle_5) - - bundle_6 = Bundle(data={'datetime': ['a', 'list', 'used', 'to', 'crash']}) - field_6 = DateTimeField() - field_6.instance_name = 'datetime' - self.assertRaises(ApiFieldError, field_6.hydrate, bundle_6) - - def test_model_resource_correct_association(self): - api_field = ModelResource.api_field_from_django_field(models.DateTimeField()) - self.assertEqual(api_field, DateTimeField) - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - filtering = { - 'id': ALL, - 'username': ALL, - } - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/users/' - - return '/api/v1/users/%s/' % bundle_or_obj.obj.id - - -class ToOneFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_init(self): - field_1 = ToOneField(UserResource, 'author') - self.assertEqual(field_1.instance_name, None) - self.assertEqual(issubclass(field_1.to, UserResource), True) - self.assertEqual(field_1.attribute, 'author') - self.assertEqual(field_1.related_name, None) - self.assertEqual(field_1.null, False) - self.assertEqual(field_1.full, False) - self.assertEqual(field_1.readonly, False) - self.assertEqual(field_1.help_text, 'A single related resource. Can be either a URI or set of nested resource data.') - - field_2 = ToOneField(UserResource, 'author', null=True, help_text="Points to a User.") - self.assertEqual(field_2.instance_name, None) - self.assertEqual(issubclass(field_2.to, UserResource), True) - self.assertEqual(field_2.attribute, 'author') - self.assertEqual(field_2.related_name, None) - self.assertEqual(field_2.null, True) - self.assertEqual(field_2.full, False) - self.assertEqual(field_2.readonly, False) - self.assertEqual(field_2.help_text, 'Points to a User.') - - field_3 = ToOneField(UserResource, 'author', default=1, null=True, help_text="Points to a User.") - self.assertEqual(field_3.instance_name, None) - self.assertEqual(issubclass(field_3.to, UserResource), True) - self.assertEqual(field_3.attribute, 'author') - self.assertEqual(field_3.related_name, None) - self.assertEqual(field_3.null, True) - self.assertEqual(field_3.default, 1) - self.assertEqual(field_3.full, False) - self.assertEqual(field_3.readonly, False) - self.assertEqual(field_3.help_text, 'Points to a User.') - - field_4 = ToOneField(UserResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.") - self.assertEqual(field_4.instance_name, None) - self.assertEqual(issubclass(field_4.to, UserResource), True) - self.assertEqual(field_4.attribute, 'author') - self.assertEqual(field_4.related_name, None) - self.assertEqual(field_4.null, True) - self.assertEqual(field_4.default, 1) - self.assertEqual(field_4.full, False) - self.assertEqual(field_4.readonly, True) - self.assertEqual(field_4.help_text, 'Points to a User.') - - field_5 = ToOneField(UserResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in="list") - self.assertEqual(field_5.use_in, 'list') - - field_6 = ToOneField(UserResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in="detail") - self.assertEqual(field_6.use_in, 'detail') - - def use_in_callable(x): - return True - field_7 = ToOneField(UserResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in=use_in_callable) - self.assertTrue(field_7.use_in is use_in_callable) - - field_8 = ToOneField(UserResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in="foo") - self.assertEqual(field_8.use_in, 'all') - - def test_dehydrated_type(self): - field_1 = ToOneField(UserResource, 'author') - self.assertEqual(field_1.dehydrated_type, 'related') - - def test_has_default(self): - field_1 = ToOneField(UserResource, 'author') - self.assertEqual(field_1.has_default(), False) - - field_1 = ToOneField(UserResource, 'author', default=1) - self.assertEqual(field_1.has_default(), True) - - def test_default(self): - field_1 = ToOneField(UserResource, 'author') - self.assertTrue(isinstance(field_1.default, NOT_PROVIDED)) - - field_2 = ToOneField(UserResource, 'author', default=1) - self.assertEqual(field_2.default, 1) - - def test_dehydrate(self): - note = Note() - bundle = Bundle(obj=note) - - field_1 = ToOneField(UserResource, 'author') - self.assertRaises(ApiFieldError, field_1.dehydrate, bundle) - - field_2 = ToOneField(UserResource, 'author', null=True) - self.assertEqual(field_2.dehydrate(bundle), None) - - note = Note.objects.get(pk=1) - request = MockRequest() - request.path = "/api/v1/notes/1/" - bundle = Bundle(obj=note, request=request) - - field_3 = ToOneField(UserResource, 'author') - self.assertEqual(field_3.dehydrate(bundle), '/api/v1/users/1/') - - field_4 = ToOneField(UserResource, 'author', full=True) - user_bundle = field_4.dehydrate(bundle) - self.assertEqual(isinstance(user_bundle, Bundle), True) - self.assertEqual(user_bundle.data['username'], u'johndoe') - self.assertEqual(user_bundle.data['email'], u'john@doe.com') - - def test_dehydrate_with_callable(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - field_1 = ToOneField(UserResource, lambda bundle: User.objects.get(pk=1)) - self.assertEqual(field_1.dehydrate(bundle), '/api/v1/users/1/') - - field_2 = ToManyField(UserResource, lambda bundle: User.objects.filter(pk=1)) - self.assertEqual(field_2.dehydrate(bundle), ['/api/v1/users/1/']) - - field_3 = ToOneField(UserResource, lambda bundle: None) - self.assertRaises(ApiFieldError, field_3.dehydrate, bundle) - - def test_dehydrate_full_detail_list(self): - note = Note.objects.get(pk=1) - request = MockRequest() - bundle = Bundle(obj=note, request=request) - - # details path with full_list=False - request.path = "/api/v1/notes/" - field_1 = ToOneField(UserResource, 'author', full=True, full_list=False) - self.assertEqual(field_1.dehydrate(bundle), '/api/v1/users/1/') - - # list path with full_detail=False - request.path = "/api/v1/notes/1/" - field_1 = ToOneField(UserResource, 'author', full=True, full_detail=False) - self.assertEqual(field_1.dehydrate(bundle, for_list=False), '/api/v1/users/1/') - - def test_hydrate(self): - note = Note() - bundle = Bundle(obj=note) - - # With no value or nullable, we should get an ``ApiFieldError``. - field_1 = ToOneField(UserResource, 'author') - self.assertRaises(ApiFieldError, field_1.hydrate, bundle) - - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - # The nullable case. - field_2 = ToOneField(UserResource, 'author', null=True) - field_2.instance_name = 'fk' - bundle.data['fk'] = None - self.assertEqual(field_2.hydrate(bundle), None) - - # Wrong resource URI. - field_3 = ToOneField(UserResource, 'author') - field_3.instance_name = 'fk' - bundle.data['fk'] = '/api/v1/users/abc/' - self.assertRaises(NotFound, field_3.hydrate, bundle) - - # A real, live attribute! - field_4 = ToOneField(UserResource, 'author') - field_4.instance_name = 'fk' - bundle.data['fk'] = '/api/v1/users/1/' - fk_bundle = field_4.hydrate(bundle) - self.assertEqual(fk_bundle.data['username'], u'johndoe') - self.assertEqual(fk_bundle.data['email'], u'john@doe.com') - self.assertEqual(fk_bundle.obj.username, u'johndoe') - self.assertEqual(fk_bundle.obj.email, u'john@doe.com') - - field_5 = ToOneField(UserResource, 'author') - field_5.instance_name = 'fk' - bundle.data['fk'] = { - 'username': u'mistersmith', - 'email': u'smith@example.com', - 'password': u'foobar', - } - fk_bundle = field_5.hydrate(bundle) - self.assertEqual(fk_bundle.data['username'], u'mistersmith') - self.assertEqual(fk_bundle.data['email'], u'smith@example.com') - self.assertEqual(fk_bundle.obj.username, u'mistersmith') - self.assertEqual(fk_bundle.obj.email, u'smith@example.com') - - # Regression - Make sure Unicode keys get converted to regular strings - # so that we can **kwargs them. - field_6 = ToOneField(UserResource, 'author') - field_6.instance_name = 'fk' - bundle.data['fk'] = { - u'username': u'mistersmith', - u'email': u'smith@example.com', - u'password': u'foobar', - } - fk_bundle = field_6.hydrate(bundle) - self.assertEqual(fk_bundle.data['username'], u'mistersmith') - self.assertEqual(fk_bundle.data['email'], u'smith@example.com') - self.assertEqual(fk_bundle.obj.username, u'mistersmith') - self.assertEqual(fk_bundle.obj.email, u'smith@example.com') - - # Attribute & null regression test. - # First, simulate data missing from the bundle & ``null=True``. - # Use a Note with NO author, so that the lookup for the related - # author fails. - note = Note.objects.create( - title='Biplanes for all!', - slug='biplanes-for-all', - content='Somewhere, east of Manhattan, will lie the mythical land of planes with more one wing...' - ) - bundle = Bundle(obj=note) - field_7 = ToOneField(UserResource, 'notinbundle', null=True) - field_7.instance_name = 'notinbundle' - self.assertEqual(field_7.hydrate(bundle), None) - # Then do something in the bundle also with ``null=True``. - field_8 = ToOneField(UserResource, 'author', null=True) - field_8.instance_name = 'author' - fk_bundle = field_8.hydrate(bundle) - self.assertEqual(field_8.hydrate(bundle), None) - # Then use an unsaved object in the bundle also with ``null=True``. - field_9 = ToOneField(UserResource, 'author', null=True) - field_9.instance_name = 'author' - self.assertEqual(field_9.hydrate(bundle), None) - - # The blank case. - field_10 = ToOneField(UserResource, 'fk', blank=True) - field_10.instance_name = 'fk' - self.assertEqual(field_10.hydrate(bundle), None) - - bundle.data['author'] = '/api/v1/users/1/' - field_11 = ToOneField(UserResource, 'author', blank=True) - field_11.instance_name = 'author' - fk_bundle = field_11.hydrate(bundle) - self.assertEqual(fk_bundle.obj.username, 'johndoe') - - # The readonly case. - field_12 = ToOneField(UserResource, 'author', readonly=True) - field_12.instance_name = 'author' - self.assertEqual(field_12.hydrate(bundle), None) - - # A related object. - field_13 = ToOneField(UserResource, 'author') - field_13.instance_name = 'fk' - bundle.related_obj = User.objects.get(pk=1) - bundle.related_name = 'author' - fk_bundle = field_13.hydrate(bundle) - self.assertEqual(fk_bundle.obj.username, u'johndoe') - self.assertEqual(fk_bundle.obj.email, u'john@doe.com') - - def test_resource_from_uri(self): - ur = UserResource() - field_1 = ToOneField(UserResource, 'author') - fk_bundle = field_1.resource_from_uri(ur, '/api/v1/users/1/') - self.assertEqual(fk_bundle.data['username'], u'johndoe') - self.assertEqual(fk_bundle.data['email'], u'john@doe.com') - self.assertEqual(fk_bundle.obj.username, u'johndoe') - self.assertEqual(fk_bundle.obj.email, u'john@doe.com') - - fk_bundle = field_1.resource_from_uri(ur, '/api/v1/users/1/', related_obj='Foo', related_name='Bar') - self.assertEqual(fk_bundle.related_obj, None) - self.assertEqual(fk_bundle.related_name, None) - - def test_resource_from_data(self): - ur = UserResource() - field_1 = ToOneField(UserResource, 'author') - fk_bundle = field_1.resource_from_data(ur, { - 'username': u'mistersmith', - 'email': u'smith@example.com', - 'password': u'foobar', - }) - self.assertEqual(fk_bundle.data['username'], u'mistersmith') - self.assertEqual(fk_bundle.data['email'], u'smith@example.com') - self.assertEqual(fk_bundle.obj.username, u'mistersmith') - self.assertEqual(fk_bundle.obj.email, u'smith@example.com') - - fk_bundle = field_1.resource_from_data(ur, { - 'username': u'mistersmith', - 'email': u'smith@example.com', - 'password': u'foobar', - }, related_obj='Foo', related_name='Bar') - self.assertEqual(fk_bundle.related_obj, 'Foo') - self.assertEqual(fk_bundle.related_name, 'Bar') - - def test_resource_from_pk(self): - user = User.objects.get(pk=1) - ur = UserResource() - field_1 = ToOneField(UserResource, 'author') - fk_bundle = field_1.resource_from_pk(ur, user) - self.assertEqual(fk_bundle.data['username'], u'johndoe') - self.assertEqual(fk_bundle.data['email'], u'john@doe.com') - self.assertEqual(fk_bundle.obj.username, u'johndoe') - self.assertEqual(fk_bundle.obj.email, u'john@doe.com') - - fk_bundle = field_1.resource_from_pk(ur, user, related_obj='Foo', related_name='Bar') - self.assertEqual(fk_bundle.related_obj, None) - self.assertEqual(fk_bundle.related_name, None) - - def test_traversed_attribute_dehydrate(self): - user = User.objects.get(pk=1) - note = Note.objects.create(author=user) - mediabit = MediaBit(note=note) - bundle = Bundle(obj=mediabit) - - field_1 = ToOneField(UserResource, 'note__author') - field_1.instance_name = 'fk' - self.assertEqual(field_1.dehydrate(bundle), '/api/v1/users/1/') - - field_2 = ToOneField(UserResource, 'fakefield__author') - field_2.instance_name = 'fk' - self.assertRaises(ApiFieldError, field_2.hydrate, bundle) - - -class SubjectResource(ModelResource): - class Meta: - resource_name = 'subjects' - queryset = Subject.objects.all() - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/subjects/' - - return '/api/v1/subjects/%s/' % bundle_or_obj.obj.id - - -class MediaBitResource(ModelResource): - class Meta: - resource_name = 'mediabits' - queryset = MediaBit.objects.all() - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/mediabits/' - - return '/api/v1/mediabits/%s/' % bundle_or_obj.obj.id - - -class ToManyFieldTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def setUp(self): - self.note_1 = Note.objects.get(pk=1) - self.note_2 = Note.objects.get(pk=2) - self.note_3 = Note.objects.get(pk=3) - - self.subject_1 = Subject.objects.create( - name='News', - url='/news/' - ) - self.subject_2 = Subject.objects.create( - name='Photos', - url='/photos/' - ) - self.subject_3 = Subject.objects.create( - name='Personal Interest', - url='/news/personal-interest/' - ) - - self.note_1.subjects.add(self.subject_1) - self.note_1.subjects.add(self.subject_2) - self.note_2.subjects.add(self.subject_1) - self.note_2.subjects.add(self.subject_3) - - def test_init(self): - field_1 = ToManyField(SubjectResource, 'subjects') - self.assertEqual(field_1.instance_name, None) - self.assertEqual(issubclass(field_1.to, SubjectResource), True) - self.assertEqual(field_1.attribute, 'subjects') - self.assertEqual(field_1.related_name, None) - self.assertEqual(field_1.null, False) - self.assertEqual(field_1.full, False) - self.assertEqual(field_1.readonly, False) - self.assertEqual(field_1.help_text, 'Many related resources. Can be either a list of URIs or list of individually nested resource data.') - - field_2 = ToManyField(SubjectResource, 'subjects', null=True, help_text='Points to many Subjects.') - self.assertEqual(field_2.instance_name, None) - self.assertEqual(issubclass(field_2.to, SubjectResource), True) - self.assertEqual(field_2.attribute, 'subjects') - self.assertEqual(field_2.related_name, None) - self.assertEqual(field_2.null, True) - self.assertEqual(field_2.full, False) - self.assertEqual(field_2.readonly, False) - self.assertEqual(field_2.help_text, 'Points to many Subjects.') - - field_3 = ToManyField(SubjectResource, 'subjects', default=1, null=True, help_text='Points to many Subjects.') - self.assertEqual(field_3.instance_name, None) - self.assertEqual(issubclass(field_3.to, SubjectResource), True) - self.assertEqual(field_3.attribute, 'subjects') - self.assertEqual(field_3.related_name, None) - self.assertEqual(field_3.null, True) - self.assertEqual(field_3.default, 1) - self.assertEqual(field_3.full, False) - self.assertEqual(field_3.readonly, False) - self.assertEqual(field_3.help_text, 'Points to many Subjects.') - - field_4 = ToManyField(SubjectResource, 'subjects', default=1, null=True, readonly=True, help_text='Points to many Subjects.') - self.assertEqual(field_4.instance_name, None) - self.assertEqual(issubclass(field_4.to, SubjectResource), True) - self.assertEqual(field_4.attribute, 'subjects') - self.assertEqual(field_4.related_name, None) - self.assertEqual(field_4.null, True) - self.assertEqual(field_4.default, 1) - self.assertEqual(field_4.full, False) - self.assertEqual(field_4.readonly, True) - self.assertEqual(field_4.help_text, 'Points to many Subjects.') - - field_5 = ToManyField(SubjectResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in="list") - self.assertEqual(field_5.use_in, 'list') - - field_6 = ToManyField(SubjectResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in="detail") - self.assertEqual(field_6.use_in, 'detail') - - def use_in_callable(x): - return True - field_7 = ToManyField(SubjectResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in=use_in_callable) - self.assertTrue(field_7.use_in is use_in_callable) - - field_8 = ToManyField(SubjectResource, 'author', default=1, null=True, readonly=True, help_text="Points to a User.", use_in="foo") - self.assertEqual(field_8.use_in, 'all') - - def test_dehydrated_type(self): - field_1 = ToManyField(SubjectResource, 'subjects') - self.assertEqual(field_1.dehydrated_type, 'related') - - def test_has_default(self): - field_1 = ToManyField(SubjectResource, 'subjects') - self.assertEqual(field_1.has_default(), False) - - field_2 = ToManyField(SubjectResource, 'subjects', default=1) - self.assertEqual(field_2.has_default(), True) - - def test_default(self): - field_1 = ToManyField(SubjectResource, 'subjects') - self.assertTrue(isinstance(field_1.default, NOT_PROVIDED)) - - field_2 = ToManyField(SubjectResource, 'subjects', default=1) - self.assertEqual(field_2.default, 1) - - def test_dehydrate(self): - note = Note() - bundle_1 = Bundle(obj=note) - field_1 = ToManyField(SubjectResource, 'subjects') - field_1.instance_name = 'm2m' - - with self.assertRaises(ApiFieldError): - field_1.dehydrate(bundle_1) - - field_2 = ToManyField(SubjectResource, 'subjects', null=True) - field_2.instance_name = 'm2m' - self.assertEqual(field_2.dehydrate(bundle_1), []) - - field_3 = ToManyField(SubjectResource, 'subjects') - field_3.instance_name = 'm2m' - bundle_3 = Bundle(obj=self.note_1) - self.assertEqual(field_3.dehydrate(bundle_3), ['/api/v1/subjects/1/', '/api/v1/subjects/2/']) - - field_4 = ToManyField(SubjectResource, 'subjects', full=True) - field_4.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/%(pk)s/" % {'pk': self.note_1.pk} - bundle_4 = Bundle(obj=self.note_1, request=request) - subject_bundle_list = field_4.dehydrate(bundle_4) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(isinstance(subject_bundle_list[0], Bundle), True) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - self.assertEqual(isinstance(subject_bundle_list[1], Bundle), True) - self.assertEqual(subject_bundle_list[1].data['name'], u'Photos') - self.assertEqual(subject_bundle_list[1].data['url'], u'/photos/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Photos') - self.assertEqual(subject_bundle_list[1].obj.url, u'/photos/') - - field_5 = ToManyField(SubjectResource, 'subjects') - field_5.instance_name = 'm2m' - bundle_5 = Bundle(obj=self.note_2) - self.assertEqual(field_5.dehydrate(bundle_5), ['/api/v1/subjects/1/', '/api/v1/subjects/3/']) - - field_6 = ToManyField(SubjectResource, 'subjects') - field_6.instance_name = 'm2m' - bundle_6 = Bundle(obj=self.note_3) - self.assertEqual(field_6.dehydrate(bundle_6), []) - - # Regression for missing variable initialization. - field_7 = ToManyField(SubjectResource, None) - field_7.instance_name = 'm2m' - bundle_7 = Bundle(obj=self.note_3) - - with self.assertRaises(ApiFieldError): - # ToManyField requires an attribute of some type. - field_7.dehydrate(bundle_7) - - def test_dehydrate_full_detail_list(self): - # details path with full_detail=False - field_1 = ToManyField(SubjectResource, 'subjects', full=True, full_detail=False) - field_1.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/%(pk)s/" % {'pk': self.note_1.pk} - bundle_1 = Bundle(obj=self.note_1, request=request) - self.assertEqual(field_1.dehydrate(bundle_1, for_list=False), ['/api/v1/subjects/1/', '/api/v1/subjects/2/']) - - # list path with full_detail=False - field_2 = ToManyField(SubjectResource, 'subjects', full=True, full_detail=False) - field_2.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/" - bundle_2 = Bundle(obj=self.note_1, request=request) - subject_bundle_list = field_2.dehydrate(bundle_2) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(isinstance(subject_bundle_list[0], Bundle), True) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - self.assertEqual(isinstance(subject_bundle_list[1], Bundle), True) - self.assertEqual(subject_bundle_list[1].data['name'], u'Photos') - self.assertEqual(subject_bundle_list[1].data['url'], u'/photos/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Photos') - self.assertEqual(subject_bundle_list[1].obj.url, u'/photos/') - - # list path with full_list=False - field_3 = ToManyField(SubjectResource, 'subjects', full=True, full_list=False) - field_3.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/" - bundle_3 = Bundle(obj=self.note_1, request=request) - self.assertEqual(field_3.dehydrate(bundle_3), ['/api/v1/subjects/1/', '/api/v1/subjects/2/']) - - # detail path with full_list=False - field_4 = ToManyField(SubjectResource, 'subjects', full=True, full_list=False) - field_4.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/%(pk)s/" % {'pk': self.note_1.pk} - bundle_4 = Bundle(obj=self.note_1, request=request) - subject_bundle_list = field_4.dehydrate(bundle_4, for_list=False) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(isinstance(subject_bundle_list[0], Bundle), True) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - self.assertEqual(isinstance(subject_bundle_list[1], Bundle), True) - self.assertEqual(subject_bundle_list[1].data['name'], u'Photos') - self.assertEqual(subject_bundle_list[1].data['url'], u'/photos/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Photos') - self.assertEqual(subject_bundle_list[1].obj.url, u'/photos/') - - # list url with callable returning True - field_5 = ToManyField(SubjectResource, 'subjects', full=True, full_list=lambda x: True) - field_5.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/" - bundle_5 = Bundle(obj=self.note_1, request=request) - subject_bundle_list = field_5.dehydrate(bundle_5) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(isinstance(subject_bundle_list[0], Bundle), True) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - self.assertEqual(isinstance(subject_bundle_list[1], Bundle), True) - self.assertEqual(subject_bundle_list[1].data['name'], u'Photos') - self.assertEqual(subject_bundle_list[1].data['url'], u'/photos/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Photos') - self.assertEqual(subject_bundle_list[1].obj.url, u'/photos/') - - # list url with callable returning False - field_6 = ToManyField(SubjectResource, 'subjects', full=True, full_list=lambda x: False) - field_6.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/" - bundle_6 = Bundle(obj=self.note_1, request=request) - self.assertEqual(field_6.dehydrate(bundle_6), ['/api/v1/subjects/1/', '/api/v1/subjects/2/']) - - # detail url with callable returning True - field_7 = ToManyField(SubjectResource, 'subjects', full=True, full_detail=lambda x: True) - field_7.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/%(pk)s/" % {'pk': self.note_1.pk} - bundle_7 = Bundle(obj=self.note_1, request=request) - subject_bundle_list = field_7.dehydrate(bundle_7) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(isinstance(subject_bundle_list[0], Bundle), True) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - self.assertEqual(isinstance(subject_bundle_list[1], Bundle), True) - self.assertEqual(subject_bundle_list[1].data['name'], u'Photos') - self.assertEqual(subject_bundle_list[1].data['url'], u'/photos/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Photos') - self.assertEqual(subject_bundle_list[1].obj.url, u'/photos/') - - # detail url with callable returning False - field_8 = ToManyField(SubjectResource, 'subjects', full=True, full_detail=lambda x: False) - field_8.instance_name = 'm2m' - request = MockRequest() - request.path = "/api/v1/subjects/%(pk)s/" % {'pk': self.note_1.pk} - bundle_8 = Bundle(obj=self.note_1, request=request) - self.assertEqual(field_8.dehydrate(bundle_8, for_list=False), ['/api/v1/subjects/1/', '/api/v1/subjects/2/']) - - # detail url with full_detail=True and get parameters - field_9 = ToManyField(SubjectResource, 'subjects', full=True, full_detail=True) - field_9.instance_name = 'm2m' - request = HttpRequest() - request.method = "GET" - request.GET = {"foo": "bar"} - request.META["QUERY_STRING"] = "foo=bar" - request.path = "/api/v1/subjects/%(pk)s/" % {'pk': self.note_1.pk} - bundle_9 = Bundle(obj=self.note_1, request=request) - subject_bundle_list = field_9.dehydrate(bundle_9) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(isinstance(subject_bundle_list[0], Bundle), True) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - self.assertEqual(isinstance(subject_bundle_list[1], Bundle), True) - self.assertEqual(subject_bundle_list[1].data['name'], u'Photos') - self.assertEqual(subject_bundle_list[1].data['url'], u'/photos/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Photos') - self.assertEqual(subject_bundle_list[1].obj.url, u'/photos/') - - def test_dehydrate_with_callable(self): - bundle_1 = Bundle(obj=self.note_2) - field_1 = ToManyField(SubjectResource, attribute=lambda bundle: Subject.objects.filter(notes=bundle.obj, name__startswith='Personal')) - field_1.instance_name = 'm2m' - - self.assertEqual(field_1.dehydrate(bundle_1), ['/api/v1/subjects/3/']) - - def test_hydrate(self): - note = Note.objects.get(pk=1) - bundle = Bundle(obj=note) - - # With no value or nullable, we should get an ``ApiFieldError``. - field_1 = ToManyField(SubjectResource, 'subjects') - field_1.instance_name = 'm2m' - self.assertRaises(ApiFieldError, field_1.hydrate_m2m, bundle) - - # The nullable case. - field_2 = ToManyField(SubjectResource, 'subjects', null=True) - field_2.instance_name = 'm2m' - empty_bundle = Bundle() - self.assertEqual(field_2.hydrate_m2m(empty_bundle), []) - - field_3 = ToManyField(SubjectResource, 'subjects', null=True) - field_3.instance_name = 'm2m' - bundle_3 = Bundle(data={'m2m': []}) - self.assertEqual(field_3.hydrate_m2m(bundle_3), []) - - # Wrong resource URI. - field_4 = ToManyField(SubjectResource, 'subjects') - field_4.instance_name = 'm2m' - bundle_4 = Bundle(data={'m2m': ['/api/v1/subjects/abc/']}) - self.assertRaises(NotFound, field_4.hydrate_m2m, bundle_4) - - # A real, live attribute! - field_5 = ToManyField(SubjectResource, 'subjects') - field_5.instance_name = 'm2m' - bundle_5 = Bundle(data={'m2m': ['/api/v1/subjects/1/']}) - subject_bundle_list = field_5.hydrate_m2m(bundle_5) - self.assertEqual(len(subject_bundle_list), 1) - self.assertEqual(subject_bundle_list[0].data['name'], u'News') - self.assertEqual(subject_bundle_list[0].data['url'], u'/news/') - self.assertEqual(subject_bundle_list[0].obj.name, u'News') - self.assertEqual(subject_bundle_list[0].obj.url, u'/news/') - - field_6 = ToManyField(SubjectResource, 'subjects') - field_6.instance_name = 'm2m' - bundle_6 = Bundle(data={'m2m': [ - { - 'name': u'Foo', - 'url': u'/foo/', - }, - { - 'name': u'Bar', - 'url': u'/bar/', - }, - ]}) - subject_bundle_list = field_6.hydrate_m2m(bundle_6) - self.assertEqual(len(subject_bundle_list), 2) - self.assertEqual(subject_bundle_list[0].data['name'], u'Foo') - self.assertEqual(subject_bundle_list[0].data['url'], u'/foo/') - self.assertEqual(subject_bundle_list[0].obj.name, u'Foo') - self.assertEqual(subject_bundle_list[0].obj.url, u'/foo/') - self.assertEqual(subject_bundle_list[1].data['name'], u'Bar') - self.assertEqual(subject_bundle_list[1].data['url'], u'/bar/') - self.assertEqual(subject_bundle_list[1].obj.name, u'Bar') - self.assertEqual(subject_bundle_list[1].obj.url, u'/bar/') - - # The blank case. - field_7 = ToManyField(SubjectResource, 'fk', blank=True) - field_7.instance_name = 'fk' - self.assertEqual(field_7.hydrate(bundle_6), None) - - field_8 = ToManyField(SubjectResource, 'm2m', blank=True) - field_8.instance_name = 'm2m' - subject_bundle_list_2 = field_8.hydrate_m2m(bundle_6) - self.assertEqual(len(subject_bundle_list_2), 2) - self.assertEqual(subject_bundle_list_2[0].data['name'], u'Foo') - self.assertEqual(subject_bundle_list_2[0].data['url'], u'/foo/') - self.assertEqual(subject_bundle_list_2[0].obj.name, u'Foo') - self.assertEqual(subject_bundle_list_2[0].obj.url, u'/foo/') - self.assertEqual(subject_bundle_list_2[1].data['name'], u'Bar') - self.assertEqual(subject_bundle_list_2[1].data['url'], u'/bar/') - self.assertEqual(subject_bundle_list_2[1].obj.name, u'Bar') - self.assertEqual(subject_bundle_list_2[1].obj.url, u'/bar/') - - # The readonly case. - field_9 = ToManyField(SubjectResource, 'subjects', readonly=True) - field_9.instance_name = 'm2m' - self.assertEqual(field_9.hydrate(bundle_6), None) - - # A related object. - field_10 = ToManyField(MediaBitResource, 'media_bits', related_name='note') - field_10.instance_name = 'mbs' - note_1 = Note.objects.get(pk=1) - bundle_10 = Bundle(obj=note_1, data={'mbs': [ - { - 'title': 'Foo!', - }, - ]}) - media_bundle_list = field_10.hydrate_m2m(bundle_10) - self.assertEqual(len(media_bundle_list), 1) - self.assertEqual(media_bundle_list[0].obj.title, u'Foo!') - - def test_traversed_attribute_dehydrate(self): - mediabit = MediaBit(id=1, note=self.note_1) - bundle = Bundle(obj=mediabit) - - field_1 = ToManyField(SubjectResource, 'note__subjects') - field_1.instance_name = 'm2m' - self.assertEqual(field_1.dehydrate(bundle), ['/api/v1/subjects/1/', '/api/v1/subjects/2/']) - - field_2 = ToOneField(SubjectResource, 'fakefield__subjects') - field_2.instance_name = 'm2m' - self.assertRaises(ApiFieldError, field_2.hydrate, bundle) diff -Nru django-tastypie-0.13.3/tests/core/tests/http.py django-tastypie-0.14.3/tests/core/tests/http.py --- django-tastypie-0.13.3/tests/core/tests/http.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/http.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -# Basically just a sanity check to make sure things don't change from underneath us. -from django.test import TestCase -from tastypie.http import HttpAccepted, HttpBadRequest, HttpConflict,\ - HttpCreated, HttpGone, HttpMethodNotAllowed, HttpNoContent, HttpNotFound,\ - HttpNotImplemented, HttpNotModified, HttpSeeOther, HttpTooManyRequests,\ - HttpUnauthorized - - -class HttpTestCase(TestCase): - def test_various_statuses(self): - created = HttpCreated(location='http://example.com/thingy/1/') - self.assertEqual(created.status_code, 201) - self.assertEqual(created['Location'], 'http://example.com/thingy/1/') - # Regression. - created_2 = HttpCreated() - self.assertEqual(created_2.status_code, 201) - self.assertEqual(created_2['Location'], '') - accepted = HttpAccepted() - self.assertEqual(accepted.status_code, 202) - no_content = HttpNoContent() - self.assertEqual(no_content.status_code, 204) - see_other = HttpSeeOther() - self.assertEqual(see_other.status_code, 303) - not_modified = HttpNotModified() - self.assertEqual(not_modified.status_code, 304) - bad_request = HttpBadRequest() - self.assertEqual(bad_request.status_code, 400) - unauthorized = HttpUnauthorized() - self.assertEqual(unauthorized.status_code, 401) - not_found = HttpNotFound() - self.assertEqual(not_found.status_code, 404) - not_allowed = HttpMethodNotAllowed() - self.assertEqual(not_allowed.status_code, 405) - conflict = HttpConflict() - self.assertEqual(conflict.status_code, 409) - gone = HttpGone() - self.assertEqual(gone.status_code, 410) - toomanyrequests = HttpTooManyRequests() - self.assertEqual(toomanyrequests.status_code, 429) - not_implemented = HttpNotImplemented() - self.assertEqual(not_implemented.status_code, 501) diff -Nru django-tastypie-0.13.3/tests/core/tests/__init__.py django-tastypie-0.14.3/tests/core/tests/__init__.py --- django-tastypie-0.13.3/tests/core/tests/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -import warnings -warnings.simplefilter('ignore', Warning) - -from core.tests.api import * # flake8: noqa -from core.tests.authentication import * # flake8: noqa -from core.tests.authorization import * # flake8: noqa -from core.tests.cache import * # flake8: noqa -from core.tests.commands import * # flake8: noqa -from core.tests.fields import * # flake8: noqa -from core.tests.http import * # flake8: noqa -from core.tests.paginator import * # flake8: noqa -from core.tests.resources import * # flake8: noqa -from core.tests.serializers import * # flake8: noqa -from core.tests.throttle import * # flake8: noqa -from core.tests.utils import * # flake8: noqa -from core.tests.validation import * # flake8: noqa diff -Nru django-tastypie-0.13.3/tests/core/tests/manual_urls.py django-tastypie-0.14.3/tests/core/tests/manual_urls.py --- django-tastypie-0.13.3/tests/core/tests/manual_urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/manual_urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -from django.conf.urls import include, url -from core.tests.resources import NoteResource - - -note_resource = NoteResource() - -urlpatterns = [ - url(r'^', include(note_resource.urls)), -] diff -Nru django-tastypie-0.13.3/tests/core/tests/mocks.py django-tastypie-0.14.3/tests/core/tests/mocks.py --- django-tastypie-0.13.3/tests/core/tests/mocks.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/mocks.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -class MockRequest(object): - def __init__(self): - self.GET = {} - self.POST = {} - self.PUT = {} - self.DELETE = {} - self.META = {} - self.path = '' - self.method = 'GET' - - def _load_post_and_files(self, *args, **kwargs): - pass - - def get_full_path(self, *args, **kwargs): - return self.path - - def is_ajax(self): - return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' - - def set_body(self, content): - self.body = content diff -Nru django-tastypie-0.13.3/tests/core/tests/paginator.py django-tastypie-0.14.3/tests/core/tests/paginator.py --- django-tastypie-0.13.3/tests/core/tests/paginator.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/paginator.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,294 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf import settings -from django.db import connections, reset_queries -from django.http import QueryDict -from django.test import TestCase - -from tastypie.exceptions import BadRequest -from tastypie.paginator import Paginator - -from core.models import Note - - -class PaginatorTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def setUp(self): - super(PaginatorTestCase, self).setUp() - self.data_set = Note.objects.all() - self.old_debug = settings.DEBUG - settings.DEBUG = True - - def tearDown(self): - settings.DEBUG = self.old_debug - super(PaginatorTestCase, self).tearDown() - - def _get_query_count(self): - return connections['default'].queries - - def test_page1(self): - reset_queries() - self.assertEqual(len(self._get_query_count()), 0) - - paginator = Paginator({}, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=0) - - # REGRESSION: Check to make sure only part of the cache is full. - # We used to run ``len()`` on the ``QuerySet``, which would populate - # the entire result set. Owwie. - paginator.get_count() - self.assertEqual(len(self._get_query_count()), 1) - # Should be nothing in the cache. - self.assertEqual(paginator.objects._result_cache, None) - - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 0) - self.assertEqual(meta['previous'], None) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=2' in meta['next']) - self.assertEqual(meta['total_count'], 6) - - def test_page2(self): - paginator = Paginator({}, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=2) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 2) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=0' in meta['previous']) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=4' in meta['next']) - self.assertEqual(meta['total_count'], 6) - - def test_page3(self): - paginator = Paginator({}, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=4) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 4) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=2' in meta['previous']) - self.assertEqual(meta['next'], None) - self.assertEqual(meta['total_count'], 6) - - def test_page2_with_request(self): - for req in [{'offset': '2', 'limit': '2'}, - QueryDict('offset=2&limit=2')]: - paginator = Paginator(req, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=2) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 2) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=0' in meta['previous']) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=4' in meta['next']) - self.assertEqual(meta['total_count'], 6) - - def test_page3_with_request(self): - for req in [{'offset': '4', 'limit': '2'}, - QueryDict('offset=4&limit=2')]: - paginator = Paginator(req, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=4) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 4) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=2' in meta['previous']) - self.assertEqual(meta['next'], None) - self.assertEqual(meta['total_count'], 6) - - def test_large_limit(self): - paginator = Paginator({}, self.data_set, - resource_uri='/api/v1/notes/', limit=20, offset=0) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 20) - self.assertEqual(meta['offset'], 0) - self.assertEqual(meta['previous'], None) - self.assertEqual(meta['next'], None) - self.assertEqual(meta['total_count'], 6) - - def test_all(self): - paginator = Paginator({'limit': 0}, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=0) - page = paginator.page() - meta = page['meta'] - self.assertEqual(meta['limit'], 1000) - self.assertEqual(meta['offset'], 0) - self.assertEqual(meta['total_count'], 6) - self.assertEqual(len(page['objects']), 6) - - def test_complex_get(self): - request = { - 'slug__startswith': 'food', - 'format': 'json', - } - paginator = Paginator(request, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=2) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 2) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=0' in meta['previous']) - self.assertTrue('slug__startswith=food' in meta['previous']) - self.assertTrue('format=json' in meta['previous']) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=4' in meta['next']) - self.assertTrue('slug__startswith=food' in meta['next']) - self.assertTrue('format=json' in meta['next']) - self.assertEqual(meta['total_count'], 6) - - def test_limit(self): - paginator = Paginator({}, self.data_set, limit=20, offset=0) - - paginator.limit = '10' - self.assertEqual(paginator.get_limit(), 10) - - paginator.limit = None - self.assertEqual(paginator.get_limit(), 20) - - paginator.limit = 10 - self.assertEqual(paginator.get_limit(), 10) - - paginator.limit = -10 - raised = False - try: - paginator.get_limit() - except BadRequest as e: - raised = e - self.assertTrue(raised) - self.assertEqual(str(raised), "Invalid limit '-10' provided. Please provide a positive integer >= 0.") - - paginator.limit = 'hAI!' - raised = False - try: - paginator.get_limit() - except BadRequest as e: - raised = e - self.assertTrue(raised) - self.assertEqual(str(raised), "Invalid limit 'hAI!' provided. Please provide a positive integer.") - - # Test the max_limit. - paginator.limit = 1000 - self.assertEqual(paginator.get_limit(), 1000) - - paginator.limit = 1001 - self.assertEqual(paginator.get_limit(), 1000) - - paginator = Paginator({}, - self.data_set, limit=20, offset=0, max_limit=10) - self.assertEqual(paginator.get_limit(), 10) - - def test_offset(self): - paginator = Paginator({}, self.data_set, limit=20, offset=0) - - paginator.offset = '10' - self.assertEqual(paginator.get_offset(), 10) - - paginator.offset = 0 - self.assertEqual(paginator.get_offset(), 0) - - paginator.offset = 10 - self.assertEqual(paginator.get_offset(), 10) - - paginator.offset = -10 - raised = False - try: - paginator.get_offset() - except BadRequest as e: - raised = e - self.assertTrue(raised) - self.assertEqual(str(raised), "Invalid offset '-10' provided. Please provide a positive integer >= 0.") - - paginator.offset = 'hAI!' - raised = False - try: - paginator.get_offset() - except BadRequest as e: - raised = e - self.assertTrue(raised) - self.assertEqual(str(raised), "Invalid offset 'hAI!' provided. Please provide an integer.") - - def test_regression_nonqueryset(self): - paginator = Paginator({}, ['foo', 'bar', 'baz'], limit=2, offset=0) - # This would fail due to ``count`` being present on ``list`` but called - # differently. - page = paginator.page() - self.assertEqual(page['objects'], ['foo', 'bar']) - - def test_unicode_request(self): - request = { - 'slug__startswith': u'☃', - 'format': 'json', - } - paginator = Paginator(request, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=2) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 2) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=0' in meta['previous']) - self.assertTrue('slug__startswith=%E2%98%83' in meta['previous']) - self.assertTrue('format=json' in meta['previous']) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=4' in meta['next']) - self.assertTrue('slug__startswith=%E2%98%83' in meta['next']) - self.assertTrue('format=json' in meta['next']) - self.assertEqual(meta['total_count'], 6) - - request = QueryDict('slug__startswith=☃&format=json') - paginator = Paginator(request, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=2) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 2) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=0' in meta['previous']) - self.assertTrue('slug__startswith=%E2%98%83' in meta['previous']) - self.assertTrue('format=json' in meta['previous']) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=4' in meta['next']) - self.assertTrue('slug__startswith=%E2%98%83' in meta['next']) - self.assertTrue('format=json' in meta['next']) - self.assertEqual(meta['total_count'], 6) - - def test_custom_collection_name(self): - paginator = Paginator({}, self.data_set, - resource_uri='/api/v1/notes/', limit=20, offset=0, - collection_name='notes') - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 20) - self.assertEqual(meta['offset'], 0) - self.assertEqual(meta['previous'], None) - self.assertEqual(meta['next'], None) - self.assertEqual(meta['total_count'], 6) - self.assertEqual(len(paginator.page()['notes']), 6) - - def test_multiple(self): - request = QueryDict('a=1&a=2') - paginator = Paginator(request, self.data_set, - resource_uri='/api/v1/notes/', limit=2, offset=2) - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 2) - self.assertEqual(meta['offset'], 2) - self.assertTrue('limit=2' in meta['previous']) - self.assertTrue('offset=0' in meta['previous']) - self.assertTrue('a=1' in meta['previous']) - self.assertTrue('a=2' in meta['previous']) - self.assertTrue('limit=2' in meta['next']) - self.assertTrue('offset=4' in meta['next']) - self.assertTrue('a=1' in meta['next']) - self.assertTrue('a=2' in meta['next']) - - def test_max_limit(self): - paginator = Paginator({'limit': 0}, self.data_set, max_limit=10, - resource_uri='/api/v1/notes/') - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 10) - - def test_max_limit_none(self): - paginator = Paginator({'limit': 0}, self.data_set, max_limit=None, - resource_uri='/api/v1/notes/') - meta = paginator.page()['meta'] - self.assertEqual(meta['limit'], 0) diff -Nru django-tastypie-0.13.3/tests/core/tests/resources.py django-tastypie-0.14.3/tests/core/tests/resources.py --- django-tastypie-0.13.3/tests/core/tests/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5018 +0,0 @@ -import base64 -from collections import OrderedDict -import copy -import datetime -from decimal import Decimal -import json -from mock import patch, Mock -import time -from unittest import skipIf - -from django import forms -from django.contrib.auth.models import User -from django.core.cache import cache -from django.core.exceptions import FieldError, MultipleObjectsReturned -from django.core import mail -from django.core.urlresolvers import reverse -from django.http import HttpRequest, QueryDict, Http404 -from django.test import TestCase -from django.test.utils import override_settings -from django.utils import timezone -from django.utils.encoding import force_text - -from tastypie.authentication import BasicAuthentication -from tastypie.authorization import Authorization -from tastypie.bundle import Bundle -from tastypie.exceptions import ( - InvalidFilterError, InvalidSortError, ImmediateHttpResponse, BadRequest, - NotFound, UnsupportedFormat, -) -from tastypie import fields, http -from tastypie.paginator import Paginator -from tastypie.resources import ( - ALL, ALL_WITH_RELATIONS, convert_post_to_put, convert_post_to_patch, - Resource, ModelResource, -) -from tastypie.serializers import Serializer -from tastypie.throttle import CacheThrottle -from tastypie.utils import aware_datetime, make_naive -from tastypie.validation import FormValidation -from core.models import ( - Note, NoteWithEditor, Subject, MediaBit, AutoNowNote, DateRecord, Counter, - MyDefaultPKModel, MyUUIDModel, MyRelatedUUIDModel, -) -from core.tests.mocks import MockRequest -from core.utils import adjust_schema, SimpleHandler - - -class CustomSerializer(Serializer): - pass - - -class TestObject(object): - name = None - view_count = None - date_joined = None - - -class BasicResourceWithDifferentListAndDetailFields(Resource): - name = fields.CharField(attribute='name', use_in="all") - view_count = fields.IntegerField(attribute='view_count', default=0, use_in="detail") - date_joined = fields.DateTimeField(null=True, use_in="list") - - def dehydrate_date_joined(self, bundle): - if getattr(bundle.obj, 'date_joined', None) is not None: - return bundle.obj.date_joined - - if bundle.data.get('date_joined') is not None: - return bundle.data.get('date_joined') - - return aware_datetime(2010, 3, 27, 22, 30, 0) - - def hydrate_date_joined(self, bundle): - bundle.obj.date_joined = bundle.data['date_joined'] - return bundle - - def obj_get_list(self, bundle, **kwargs): - test_object_1 = TestObject() - test_object_1.name = 'Daniel' - test_object_1.view_count = 12 - test_object_1.date_joined = aware_datetime(2010, 3, 30, 9, 0, 0) - return [test_object_1] - - class Meta: - object_class = TestObject - detail_uri_name = 'name' - resource_name = 'basic' - - -class BasicResourceWithDifferentListAndDetailFieldsCallable(Resource): - name = fields.CharField(attribute='name', use_in="all") - view_count = fields.IntegerField(attribute='view_count', default=0, use_in=lambda x: True) - date_joined = fields.DateTimeField(null=True, use_in=lambda x: False) - - class Meta: - object_class = TestObject - detail_uri_name = 'name' - resource_name = 'basic' - - -class BasicResource(Resource): - name = fields.CharField(attribute='name', verbose_name="Basic name") - view_count = fields.IntegerField(attribute='view_count', default=0) - date_joined = fields.DateTimeField(null=True) - - class Meta: - object_class = TestObject - detail_uri_name = 'name' - resource_name = 'basic' - authorization = Authorization() - - def dehydrate_date_joined(self, bundle): - if getattr(bundle.obj, 'date_joined', None) is not None: - return bundle.obj.date_joined - - if bundle.data.get('date_joined') is not None: - return bundle.data.get('date_joined') - - return aware_datetime(2010, 3, 27, 22, 30, 0) - - def hydrate_date_joined(self, bundle): - bundle.obj.date_joined = bundle.data['date_joined'] - return bundle - - def get_list(self, request, **kwargs): - raise NotImplementedError - - -class AnotherBasicResource(BasicResource): - name = fields.CharField(attribute='name') - view_count = fields.IntegerField(attribute='view_count', default=0) - date_joined = fields.DateField(attribute='created') - is_active = fields.BooleanField(attribute='is_active', default=True) - aliases = fields.ListField(attribute='aliases', null=True) - meta = fields.DictField(attribute='metadata', null=True) - owed = fields.DecimalField(attribute='money_owed', null=True) - - class Meta: - object_class = TestObject - detail_uri_name = 'name' - resource_name = 'anotherbasic' - authorization = Authorization() - - def dehydrate(self, bundle): - if hasattr(bundle.obj, 'bar'): - bundle.data['bar'] = bundle.obj.bar - - bundle.data['aliases'] = ['Mr. Smith', 'John Doe'] - bundle.data['meta'] = {'threat': 'high'} - bundle.data['owed'] = Decimal('102.57') - return bundle - - def hydrate(self, bundle): - if 'bar' in bundle.data: - bundle.obj.bar = 'O HAI BAR!' - - return bundle - - -class NoUriBasicResource(BasicResource): - name = fields.CharField(attribute='name') - view_count = fields.IntegerField(attribute='view_count', default=0) - date_joined = fields.DateTimeField(null=True) - - class Meta: - object_class = TestObject - detail_uri_name = 'name' - include_resource_uri = False - authorization = Authorization() - - -class NullableNameResource(Resource): - name = fields.CharField(attribute='name', null=True) - - class Meta: - object_class = TestObject - detail_uri_name = 'name' - resource_name = 'nullable_name' - authorization = Authorization() - - -class MangledBasicResource(BasicResource): - class Meta: - object_class = TestObject - detail_uri_name = 'name' - resource_name = 'mangledbasic' - authorization = Authorization() - - def alter_list_data_to_serialize(self, request, data_dict): - if isinstance(data_dict, dict): - if 'meta' in data_dict: - # Get rid of the "meta". - del(data_dict['meta']) - # Rename the objects. - data_dict['testobjects'] = copy.copy(data_dict['objects']) - del(data_dict['objects']) - - return data_dict - - def alter_deserialized_detail_data(self, request, bundle_or_list): - # Automatically shove in the user. - if isinstance(bundle_or_list, Bundle): - # Handle the detail. - bundle_or_list.data['user'] = request.user - elif isinstance(bundle_or_list, list): - # Handle the list. - for obj_data in bundle_or_list: - obj_data['user'] = request.user - - return bundle_or_list - - -class MROBaseFieldResourceA(Resource): - test = fields.CharField(default='test_a') - - -class MROBaseFieldResourceB(Resource): - test = fields.CharField(default='test_b') - name = fields.CharField(default='Mr. Field') - - -class MROFieldResource(MROBaseFieldResourceA, MROBaseFieldResourceB): - pass - - -class ConvertTestCase(TestCase): - def test_to_put(self): - request = HttpRequest() - request.method = 'PUT' - # Obviously not the right data, but we just need to make sure it gets - # removed. - request._post = 'foo' - request._files = 'bar' - request.POST = { - 'test': 'thing' - } - # Make Django happy. - request._read_started = False - request._raw_post_data = request._body = '' - - modified = convert_post_to_put(request) - self.assertEqual(modified.method, 'PUT') - self.assertEqual(len(modified._post), 0) - self.assertEqual(len(modified._files), 0) - self.assertEqual(modified.POST, {'test': 'thing'}) - self.assertEqual(modified.PUT, {'test': 'thing'}) - - def test_to_patch(self): - request = HttpRequest() - request.method = 'PATCH' - # Obviously not the right data, but we just need to make sure it gets - # removed. - request._post = 'foo' - request._files = 'bar' - request.POST = { - 'test': 'thing' - } - # Make Django happy. - request._read_started = False - request._raw_post_data = request._body = '' - - modified = convert_post_to_patch(request) - self.assertEqual(modified.method, 'PATCH') - self.assertEqual(len(modified._post), 0) - self.assertEqual(len(modified._files), 0) - self.assertEqual(modified.POST, {'test': 'thing'}) - self.assertEqual(modified.PATCH, {'test': 'thing'}) - - -class ResourceTestCase(TestCase): - def test_deserialize(self): - request = MockRequest() - request.META['CONTENT_TYPE'] = 'application/xml' - request.set_body('') - - basic = BasicResource() - data = basic.deserialize(request, request.body) - - self.assertEqual(data, []) - - def test_deserialize_no_contenttype_header(self): - request = MockRequest() - request.set_body('[]') - - basic = BasicResource() - data = basic.deserialize(request, request.body) - - self.assertEqual(data, []) - - def test_fields(self): - basic = BasicResource() - self.assertEqual(len(basic.fields), 4) - self.assert_('name' in basic.fields) - self.assertEqual(isinstance(basic.fields['name'], fields.CharField), True) - self.assertEqual(basic.fields['name']._resource, basic.__class__) - self.assertEqual(basic.fields['name'].instance_name, 'name') - self.assert_('view_count' in basic.fields) - self.assertEqual(isinstance(basic.fields['view_count'], fields.IntegerField), True) - self.assertEqual(basic.fields['view_count']._resource, basic.__class__) - self.assertEqual(basic.fields['view_count'].instance_name, 'view_count') - self.assert_('date_joined' in basic.fields) - self.assertEqual(isinstance(basic.fields['date_joined'], fields.DateTimeField), True) - self.assertEqual(basic.fields['date_joined']._resource, basic.__class__) - self.assertEqual(basic.fields['date_joined'].instance_name, 'date_joined') - self.assert_('resource_uri' in basic.fields) - self.assertEqual(isinstance(basic.fields['resource_uri'], fields.CharField), True) - self.assertEqual(basic.fields['resource_uri']._resource, basic.__class__) - self.assertEqual(basic.fields['resource_uri'].instance_name, 'resource_uri') - self.assertEqual(basic._meta.resource_name, 'basic') - - another = AnotherBasicResource() - self.assertEqual(len(another.fields), 8) - self.assert_('name' in another.fields) - self.assertEqual(isinstance(another.name, fields.CharField), True) - self.assertEqual(another.fields['name']._resource, another.__class__) - self.assertEqual(another.fields['name'].instance_name, 'name') - self.assert_('view_count' in another.fields) - self.assertEqual(isinstance(another.view_count, fields.IntegerField), True) - self.assertEqual(another.fields['view_count']._resource, another.__class__) - self.assertEqual(another.fields['view_count'].instance_name, 'view_count') - self.assert_('date_joined' in another.fields) - self.assertEqual(isinstance(another.date_joined, fields.DateField), True) - self.assertEqual(another.fields['date_joined']._resource, another.__class__) - self.assertEqual(another.fields['date_joined'].instance_name, 'date_joined') - self.assert_('is_active' in another.fields) - self.assertEqual(isinstance(another.is_active, fields.BooleanField), True) - self.assertEqual(another.fields['is_active']._resource, another.__class__) - self.assertEqual(another.fields['is_active'].instance_name, 'is_active') - self.assert_('aliases' in another.fields) - self.assertEqual(isinstance(another.aliases, fields.ListField), True) - self.assertEqual(another.fields['aliases']._resource, another.__class__) - self.assertEqual(another.fields['aliases'].instance_name, 'aliases') - self.assert_('meta' in another.fields) - self.assertEqual(isinstance(another.meta, fields.DictField), True) - self.assertEqual(another.fields['meta']._resource, another.__class__) - self.assertEqual(another.fields['meta'].instance_name, 'meta') - self.assert_('owed' in another.fields) - self.assertEqual(isinstance(another.owed, fields.DecimalField), True) - self.assertEqual(another.fields['owed']._resource, another.__class__) - self.assertEqual(another.fields['owed'].instance_name, 'owed') - self.assert_('resource_uri' in another.fields) - self.assertEqual(isinstance(another.resource_uri, fields.CharField), True) - self.assertEqual(another.fields['resource_uri']._resource, another.__class__) - self.assertEqual(another.fields['resource_uri'].instance_name, 'resource_uri') - self.assertEqual(another._meta.resource_name, 'anotherbasic') - - nouri = NoUriBasicResource() - self.assertEqual(len(nouri.fields), 3) - self.assert_('name' in nouri.fields) - self.assertEqual(isinstance(nouri.name, fields.CharField), True) - self.assertEqual(nouri.fields['name']._resource, nouri.__class__) - self.assertEqual(nouri.fields['name'].instance_name, 'name') - self.assert_('view_count' in nouri.fields) - self.assertEqual(isinstance(nouri.view_count, fields.IntegerField), True) - self.assertEqual(nouri.fields['view_count']._resource, nouri.__class__) - self.assertEqual(nouri.fields['view_count'].instance_name, 'view_count') - self.assert_('date_joined' in nouri.fields) - self.assertEqual(isinstance(nouri.date_joined, fields.DateTimeField), True) - self.assertEqual(nouri.fields['date_joined']._resource, nouri.__class__) - self.assertEqual(nouri.fields['date_joined'].instance_name, 'date_joined') - # Note - automatic resource naming. - self.assertEqual(nouri._meta.resource_name, 'nouribasic') - - def test_inheritance(self): - mrofr = MROFieldResource() - self.assertEqual(len(mrofr.fields), 3) - self.assertEqual(mrofr.fields['test'].default, 'test_a') - self.assertEqual(mrofr.fields['name'].default, 'Mr. Field') - - def test_full_dehydrate_with_use_in(self): - test_object_1 = TestObject() - test_object_1.name = 'Daniel' - test_object_1.view_count = 12 - test_object_1.date_joined = aware_datetime(2010, 3, 30, 9, 0, 0) - - basic = BasicResourceWithDifferentListAndDetailFields() - test_bundle_1 = basic.build_bundle(obj=test_object_1) - - # check hydration with details - bundle_1 = basic.full_dehydrate(test_bundle_1) - self.assertEqual(bundle_1.data['name'], 'Daniel') - self.assertEqual(bundle_1.data['view_count'], 12) - self.assertEqual(bundle_1.data.get('date_joined'), None) - - # now check dehydration with lists - test_bundle_2 = basic.build_bundle(obj=test_object_1) - - bundle_2 = basic.full_dehydrate(test_bundle_2, for_list=True) - self.assertEqual(bundle_2.data['name'], 'Daniel') - self.assertEqual(bundle_2.data.get('view_count'), None) - self.assertEqual(bundle_2.data['date_joined'].year, 2010) - self.assertEqual(bundle_2.data['date_joined'].day, 30) - - def test_full_dehydrate_with_use_in_callable(self): - test_object_1 = TestObject() - test_object_1.name = 'Daniel' - test_object_1.view_count = 12 - test_object_1.date_joined = aware_datetime(2010, 3, 30, 9, 0, 0) - - basic = BasicResourceWithDifferentListAndDetailFieldsCallable() - test_bundle_1 = basic.build_bundle(obj=test_object_1) - - # check hydration with details - bundle_1 = basic.full_dehydrate(test_bundle_1) - self.assertEqual(bundle_1.data['name'], 'Daniel') - self.assertEqual(bundle_1.data['view_count'], 12) - self.assertEqual(bundle_1.data.get('date_joined'), None) - - # now check dehydration with lists. Should be the same as details since - # we are using callables for the use_in - test_bundle_2 = basic.build_bundle(obj=test_object_1) - - bundle_2 = basic.full_dehydrate(test_bundle_2, for_list=True) - self.assertEqual(bundle_2.data['name'], 'Daniel') - self.assertEqual(bundle_2.data['view_count'], 12) - self.assertEqual(bundle_2.data.get('date_joined'), None) - - def test_full_dehydrate(self): - test_object_1 = TestObject() - test_object_1.name = 'Daniel' - test_object_1.view_count = 12 - test_object_1.date_joined = aware_datetime(2010, 3, 30, 9, 0, 0) - test_object_1.foo = "Hi, I'm ignored." - - basic = BasicResource() - test_bundle_1 = basic.build_bundle(obj=test_object_1) - - bundle_1 = basic.full_dehydrate(test_bundle_1) - self.assertEqual(bundle_1.data['name'], 'Daniel') - self.assertEqual(bundle_1.data['view_count'], 12) - self.assertEqual(bundle_1.data['date_joined'].year, 2010) - self.assertEqual(bundle_1.data['date_joined'].day, 30) - self.assertEqual(bundle_1.data.get('bar'), None) - - # Now check the fallback behaviors. - test_object_2 = TestObject() - test_object_2.name = 'Daniel' - basic_2 = BasicResource() - test_bundle_2 = basic_2.build_bundle(obj=test_object_2) - - bundle_2 = basic_2.full_dehydrate(test_bundle_2) - self.assertEqual(bundle_2.data['name'], 'Daniel') - self.assertEqual(bundle_2.data['view_count'], 0) - self.assertEqual(bundle_2.data['date_joined'].year, 2010) - self.assertEqual(bundle_2.data['date_joined'].day, 27) - - test_object_3 = TestObject() - test_object_3.name = 'Joe' - test_object_3.view_count = 5 - test_object_3.created = aware_datetime(2010, 3, 29, 11, 0, 0) - test_object_3.is_active = False - test_object_3.bar = "But sometimes I'm not ignored!" - another_1 = AnotherBasicResource() - test_bundle_3 = another_1.build_bundle(obj=test_object_3) - - another_bundle_1 = another_1.full_dehydrate(test_bundle_3) - self.assertEqual(another_bundle_1.data['name'], 'Joe') - self.assertEqual(another_bundle_1.data['view_count'], 5) - self.assertEqual(another_bundle_1.data['date_joined'].year, 2010) - self.assertEqual(another_bundle_1.data['date_joined'].day, 29) - self.assertEqual(another_bundle_1.data['is_active'], False) - self.assertEqual(another_bundle_1.data['aliases'], ['Mr. Smith', 'John Doe']) - self.assertEqual(another_bundle_1.data['meta'], {'threat': 'high'}) - self.assertEqual(another_bundle_1.data['owed'], Decimal('102.57')) - self.assertEqual(another_bundle_1.data['bar'], "But sometimes I'm not ignored!") - - def test_full_hydrate(self): - basic = BasicResource() - basic_bundle_1 = Bundle(data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0) - }) - - # Now load up the data. - hydrated = basic.full_hydrate(basic_bundle_1) - - self.assertEqual(hydrated.data['name'], 'Daniel') - self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], aware_datetime(2010, 2, 15, 12, 0, 0)) - self.assertEqual(hydrated.obj.name, 'Daniel') - self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, aware_datetime(2010, 2, 15, 12, 0, 0)) - - another = AnotherBasicResource() - another_bundle_1 = Bundle(data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), - 'aliases': ['test', 'test1'], - 'meta': {'foo': 'bar'}, - 'owed': '12.53', - }) - - # Now load up the data (without the ``bar`` key). - hydrated = another.full_hydrate(another_bundle_1) - - self.assertEqual(hydrated.data['name'], 'Daniel') - self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], aware_datetime(2010, 2, 15, 12, 0, 0)) - self.assertEqual(hydrated.data['aliases'], ['test', 'test1']) - self.assertEqual(hydrated.data['meta'], {'foo': 'bar'}) - self.assertEqual(hydrated.data['owed'], '12.53') - self.assertEqual(hydrated.obj.name, 'Daniel') - self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, aware_datetime(2010, 2, 15, 12, 0, 0)) - self.assertEqual(hasattr(hydrated.obj, 'bar'), False) - - another_bundle_2 = Bundle(data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), - 'bar': True, - }) - - # Now load up the data (this time with the ``bar`` key). - hydrated = another.full_hydrate(another_bundle_2) - - self.assertEqual(hydrated.data['name'], 'Daniel') - self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], aware_datetime(2010, 2, 15, 12, 0, 0)) - self.assertEqual(hydrated.obj.name, 'Daniel') - self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, aware_datetime(2010, 2, 15, 12, 0, 0)) - self.assertEqual(hydrated.obj.bar, 'O HAI BAR!') - - # Test that a nullable value with a previous non-null value - # can be set to None. - nullable = NullableNameResource() - obj = nullable._meta.object_class() - obj.name = "Daniel" - null_bundle = Bundle(obj=obj, data={'name': None}) - hydrated = nullable.full_hydrate(null_bundle) - - self.assertTrue(hydrated.obj.name is None) - - # Test that a nullable value with a previous non-null value - # is not overridden if no value was given - obj = nullable._meta.object_class() - obj.name = "Daniel" - empty_null_bundle = Bundle(obj=obj, data={}) - hydrated = nullable.full_hydrate(empty_null_bundle) - - self.assertEquals(hydrated.obj.name, "Daniel") - - def test_full_hydrate__can_put_null_to_clear_related_value(self): - class RelatedBasicResource(BasicResource): - parent = fields.ToOneField(BasicResource, 'parent', null=True, blank=True) - basic = RelatedBasicResource() - basic_bundle_1 = Bundle(data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': None, - 'parent': None - }) - basic_bundle_1.obj = Mock() - basic_bundle_1.obj.date_joined = aware_datetime(2010, 2, 15, 12, 0, 0) - basic_bundle_1.obj.parent = Mock() - - self.assertEqual(basic_bundle_1.data['date_joined'], None) - self.assertEqual(basic_bundle_1.data['parent'], None) - self.assertNotEqual(basic_bundle_1.obj.date_joined, None) - self.assertNotEqual(basic_bundle_1.obj.parent, None) - - # Now load up the data. - hydrated = basic.full_hydrate(basic_bundle_1) - - self.assertEqual(hydrated.data['name'], 'Daniel') - self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], None) - self.assertEqual(hydrated.data['parent'], None) - self.assertEqual(hydrated.obj.name, 'Daniel') - self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, None) - self.assertEqual(hydrated.obj.parent, None) - - def test_obj_get_list(self): - basic = BasicResource() - bundle = Bundle() - self.assertRaises(NotImplementedError, basic.obj_get_list, bundle) - - def test_obj_delete_list(self): - basic = BasicResource() - bundle = Bundle() - self.assertRaises(NotImplementedError, basic.obj_delete_list, bundle) - - def test_obj_get(self): - basic = BasicResource() - bundle = Bundle() - self.assertRaises(NotImplementedError, basic.obj_get, bundle, pk=1) - - def test_obj_create(self): - basic = BasicResource() - bundle = Bundle() - self.assertRaises(NotImplementedError, basic.obj_create, bundle) - - def test_obj_update(self): - basic = BasicResource() - bundle = Bundle() - self.assertRaises(NotImplementedError, basic.obj_update, bundle) - - def test_obj_delete(self): - basic = BasicResource() - bundle = Bundle() - self.assertRaises(NotImplementedError, basic.obj_delete, bundle) - - def test_rollback(self): - basic = BasicResource() - bundles_seen = [] - self.assertRaises(NotImplementedError, basic.rollback, bundles_seen) - - def test_build_schema(self): - basic = BasicResource() - schema = adjust_schema(basic.build_schema()) - expected_schema = { - 'allowed_detail_http_methods': ['get', 'post', 'put', 'delete', 'patch'], - 'allowed_list_http_methods': ['get', 'post', 'put', 'delete', 'patch'], - 'default_format': 'application/json', - 'default_limit': 20, - 'fields': { - 'date_joined': { - 'blank': False, - 'default': 'No default provided.', - 'help_text': 'A date & time as a string. Ex: "2010-11-10T03:07:43"', - 'nullable': True, - 'verbose_name': "date joined", - 'readonly': False, - 'type': 'datetime', - 'unique': False, - 'primary_key': False, - }, - 'name': { - 'blank': False, - 'default': 'No default provided.', - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'nullable': False, - 'verbose_name': "Basic name", - 'readonly': False, - 'type': 'string', - 'unique': False, - 'primary_key': False, - }, - 'resource_uri': { - 'blank': False, - 'default': 'No default provided.', - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'nullable': False, - 'verbose_name': "resource uri", - 'readonly': True, - 'type': 'string', - 'unique': False, - 'primary_key': False, - }, - 'view_count': { - 'blank': False, - 'default': 0, - 'help_text': 'Integer data. Ex: 2673', - 'nullable': False, - 'verbose_name': 'view count', - 'readonly': False, - 'type': 'integer', - 'unique': False, - 'primary_key': False, - } - } - } - self.assertEqual(schema, expected_schema) - - def test_build_schema__altered_meta(self): - basic = BasicResource() - basic._meta.ordering = ['date_joined', 'name'] - basic._meta.filtering = {'date_joined': ['gt', 'gte'], 'name': ALL} - schema = adjust_schema(basic.build_schema()) - expected_schema = { - 'filtering': { - 'name': 1, - 'date_joined': ['gt', 'gte'] - }, - 'allowed_detail_http_methods': ['get', 'post', 'put', 'delete', 'patch'], - 'ordering': ['date_joined', 'name'], - 'fields': { - 'view_count': { - 'nullable': False, - 'default': 0, - 'help_text': 'Integer data. Ex: 2673', - 'verbose_name': "view count", - 'readonly': False, - 'blank': False, - 'help_text': 'Integer data. Ex: 2673', - 'unique': False, - 'type': 'integer', - "primary_key": False - }, - 'date_joined': { - 'nullable': True, - 'default': 'No default provided.', - 'help_text': 'A date & time as a string. Ex: "2010-11-10T03:07:43"', - 'verbose_name': "date joined", - 'readonly': False, - 'blank': False, - 'help_text': 'A date & time as a string. Ex: "2010-11-10T03:07:43"', - 'unique': False, - 'type': 'datetime', - "primary_key": False - }, - 'name': { - 'nullable': False, - 'default': 'No default provided.', - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'verbose_name': "Basic name", - 'readonly': False, - 'blank': False, - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'unique': False, - 'type': 'string', - "primary_key": False - }, - 'resource_uri': { - 'nullable': False, - 'default': 'No default provided.', - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'verbose_name': "resource uri", - 'readonly': True, - 'blank': False, - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'unique': False, - 'type': 'string', - "primary_key": False - } - }, - 'default_format': 'application/json', - 'default_limit': 20, - 'allowed_list_http_methods': ['get', 'post', 'put', 'delete', 'patch'] - } - self.assertEqual(schema, expected_schema) - - def test_subclassing(self): - class CommonMeta: - default_format = 'application/xml' - - class MiniResource(Resource): - abcd = fields.CharField(default='abcd') - efgh = fields.IntegerField(default=1234) - - class Meta: - resource_name = 'mini' - - mini = MiniResource() - self.assertEqual(len(mini.fields), 3) - - class AnotherMiniResource(MiniResource): - ijkl = fields.BooleanField(default=True) - - class Meta(CommonMeta): - resource_name = 'anothermini' - - another = AnotherMiniResource() - self.assertEqual(len(another.fields), 4) - self.assertEqual(another._meta.default_format, 'application/xml') - - def test_method_check(self): - basic = BasicResource() - request = HttpRequest() - request.method = 'GET' - request.GET = {'format': 'json'} - - # No allowed methods. Kaboom. - self.assertRaises(ImmediateHttpResponse, basic.method_check, request) - - with self.assertRaises(ImmediateHttpResponse) as ctx: - basic.method_check(request) - e = ctx.exception - self.assertEqual(e.response['Allow'], '') - - # Not an allowed request. - self.assertRaises(ImmediateHttpResponse, basic.method_check, request, allowed=['post']) - - with self.assertRaises(ImmediateHttpResponse) as ctx: - basic.method_check(request, allowed=['post']) - e = ctx.exception - self.assertEqual(e.response['Allow'], 'POST') - - # Allowed (single). - request_method = basic.method_check(request, allowed=['get']) - self.assertEqual(request_method, 'get') - - # Allowed (unicode, for Python 2.* with `from __future__ import unicode_literals`) - request_method = basic.method_check(request, allowed=[u'get']) - - # Allowed (multiple). - request_method = basic.method_check(request, allowed=['post', 'get', 'put']) - self.assertEqual(request_method, 'get') - - request = HttpRequest() - request.method = 'POST' - request.POST = {'format': 'json'} - - # Not an allowed request. - self.assertRaises(ImmediateHttpResponse, basic.method_check, request, allowed=['get']) - - with self.assertRaises(ImmediateHttpResponse) as ctx: - basic.method_check(request, allowed=['get', 'put', 'delete', 'patch']) - e = ctx.exception - self.assertEqual(e.response['Allow'], 'GET,PUT,DELETE,PATCH') - - # Allowed (multiple). - request_method = basic.method_check(request, allowed=['post', 'get', 'put']) - self.assertEqual(request_method, 'post') - - def test_auth_check(self): - basic = BasicResource() - request = HttpRequest() - request.GET = {'format': 'json'} - - # Allowed (single). - try: - basic.is_authenticated(request) - except: - self.fail() - - def test_create_response(self): - basic = BasicResource() - request = HttpRequest() - request.GET = {'format': 'json'} - - data = {'hello': 'world'} - output = basic.create_response(request, data) - self.assertEqual(output.status_code, 200) - self.assertEqual(force_text(output.content), '{"hello": "world"}') - - request.GET = {'format': 'xml'} - data = {'objects': [{'hello': 'world', 'abc': 123}], 'meta': {'page': 1}} - output = basic.create_response(request, data) - self.assertEqual(output.status_code, 200) - self.assertEqual(force_text(output.content), '\n1123world') - - def test_mangled(self): - mangled = MangledBasicResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.user = 'mr_authed' - - data = Bundle(data={'hello': 'world'}) - output = mangled.alter_deserialized_detail_data(request, data) - self.assertEqual(output.data, {'hello': 'world', 'user': 'mr_authed'}) - - request.GET = {'format': 'xml'} - data = {'objects': [{'hello': 'world', 'abc': 123}], 'meta': {'page': 1}} - output = mangled.alter_list_data_to_serialize(request, data) - self.assertEqual(output, {'testobjects': [{'abc': 123, 'hello': 'world'}]}) - - def test_get_list_with_use_in(self): - basic = BasicResourceWithDifferentListAndDetailFields() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - basic_resource_list = json.loads(force_text(basic.get_list(request).content))['objects'] - self.assertEquals(basic_resource_list[0]['name'], 'Daniel') - self.assertEquals(basic_resource_list[0]['date_joined'], u'2010-03-30T09:00:00') - - self.assertNotIn('view_count', basic_resource_list[0]) - - -# ==================== -# Model-based tests... -# ==================== - -class DateRecordResource(ModelResource): - class Meta: - queryset = DateRecord.objects.all() - always_return_data = True - authorization = Authorization() - - def hydrate(self, bundle): - bundle.data['message'] = bundle.data['message'].lower() - return bundle - - def hydrate_username(self, bundle): - bundle.data['username'] = bundle.data['username'].upper() - return bundle - - -class NoteResource(ModelResource): - class Meta: - resource_name = 'notes' - authorization = Authorization() - filtering = { - 'content': ['startswith', 'exact'], - 'created': ALL, - 'title': ALL, - 'slug': ['exact'], - } - ordering = ['title', 'slug', 'resource_uri'] - queryset = Note.objects.filter(is_active=True) - serializer = Serializer(formats=['json', 'jsonp', 'xml', 'yaml', 'plist']) - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/notes/' - - return '/api/v1/notes/%s/' % bundle_or_obj.obj.id - - -class NoQuerysetNoteResource(ModelResource): - class Meta: - resource_name = 'noqsnotes' - authorization = Authorization() - filtering = { - 'name': ALL, - } - object_class = Note - - -class LightlyCustomNoteResource(NoteResource): - class Meta: - resource_name = 'noteish' - authorization = Authorization() - allowed_methods = ['get'] - queryset = Note.objects.filter(is_active=True) - - -class TinyLimitNoteResource(NoteResource): - class Meta: - limit = 3 - resource_name = 'littlenote' - authorization = Authorization() - allowed_methods = ['get'] - queryset = Note.objects.filter(is_active=True) - - -class AlwaysDataNoteResource(NoteResource): - class Meta: - resource_name = 'alwaysdatanote' - queryset = Note.objects.filter(is_active=True) - always_return_data = True - authorization = Authorization() - - -class AlwaysDataNoteResourceUseIn(NoteResource): - author = fields.CharField(attribute='author__username', use_in="detail") - constant = fields.IntegerField(default=20, use_in="list") - - class Meta: - resource_name = 'alwaysdatanote' - queryset = Note.objects.filter(is_active=True) - always_return_data = True - authorization = Authorization() - - -class NoteResourceNonUniqueDetailUriName(NoteResource): - author = fields.CharField(attribute='author__username', use_in="detail") - constant = fields.IntegerField(default=20, use_in="list") - - class Meta: - resource_name = 'nonuniqueidnote' - queryset = Note.objects.filter(is_active=True) - always_return_data = True - authorization = Authorization() - detail_uri_name = 'slug' - - -class MyDefaultPKModelResource(ModelResource): - class Meta: - resource_name = 'mydefaultpkmodel' - queryset = MyDefaultPKModel.objects.all() - always_return_data = True - authorization = Authorization() - - -if MyUUIDModel: - class MyUUIDModelResourceNonUniqueDetailUriName(ModelResource): - class Meta: - resource_name = 'nonuniqueidmyuuidmodel' - queryset = MyUUIDModel.objects.all() - always_return_data = True - authorization = Authorization() - detail_uri_name = 'anotheruuid' - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/nonuniqueidmyuuidmodel/' - - if hasattr(bundle_or_obj, 'obj'): - bundle_or_obj = bundle_or_obj.obj - return '/api/v1/nonuniqueidmyuuidmodel/%s/' % bundle_or_obj.anotheruuid - - class MyRelatedUUIDModelResource(ModelResource): - myuuidmodels = fields.ManyToManyField(MyUUIDModelResourceNonUniqueDetailUriName, 'myuuidmodels', full=True) - - class Meta: - resource_name = 'myrelateduuidmodel' - queryset = MyRelatedUUIDModel.objects.all() - always_return_data = True - authorization = Authorization() - - -class VeryCustomNoteResource(NoteResource): - author = fields.CharField(attribute='author__username') - constant = fields.IntegerField(default=20) - - class Meta: - authorization = Authorization() - limit = 50 - resource_name = 'notey' - serializer = CustomSerializer() - list_allowed_methods = ['get'] - detail_allowed_methods = ['get', 'post', 'put'] - queryset = Note.objects.all() - fields = ['title', 'content', 'created', 'is_active'] - - -class AutoNowNoteResource(ModelResource): - class Meta: - resource_name = 'autonownotes' - queryset = AutoNowNote.objects.filter(is_active=True) - authorization = Authorization() - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/autonownotes/' - - return '/api/v1/autonownotes/%s/' % bundle_or_obj.obj.id - - -class CustomPaginator(Paginator): - def page(self): - data = super(CustomPaginator, self).page() - data['extra'] = 'Some extra stuff here.' - return data - - -class CustomPageNoteResource(NoteResource): - class Meta: - limit = 10 - resource_name = 'pagey' - paginator_class = CustomPaginator - queryset = Note.objects.all() - authorization = Authorization() - - -class AlwaysUserNoteResource(NoteResource): - class Meta: - resource_name = 'noteish' - queryset = Note.objects.filter(is_active=True) - authorization = Authorization() - - def get_object_list(self, request): - return super(AlwaysUserNoteResource, self).get_object_list(request).filter(author=request.user) - - -class UseInNoteResource(NoteResource): - - content = fields.CharField(attribute='content', use_in='detail') - title = fields.CharField(attribute='title', use_in='list') - - class Meta: - queryset = Note.objects.all() - authorization = Authorization() - - -class UserResource(ModelResource): - class Meta: - queryset = User.objects.all() - authorization = Authorization() - filtering = { - 'id': ALL, - 'username': ALL, - } - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/users/' - - return '/api/v1/users/%s/' % bundle_or_obj.obj.id - - -class DetailedNoteResource(ModelResource): - user = fields.ForeignKey(UserResource, 'author') - hello_world = fields.CharField(default='world') - - class Meta: - resource_name = 'detailednotes' - filtering = { - 'content': ['startswith', 'exact'], - 'title': ALL, - 'slug': ['exact'], - 'user': ALL, - 'hello_world': ['exact'], # Note this is invalid for filtering. - } - ordering = ['title', 'slug', 'user'] - queryset = Note.objects.filter(is_active=True) - authorization = Authorization() - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/notes/' - - return '/api/v1/notes/%s/' % bundle_or_obj.obj.id - - -class DetailedNoteResourceWithHydrate(DetailedNoteResource): - def hydrate(self, bundle): - bundle.data['user'] = bundle.request.user # This should fail using TastyPie 0.9.11 if triggered in patch_list - return bundle - - -class RequiredFKNoteResource(ModelResource): - editor = fields.ForeignKey(UserResource, 'editor') - - class Meta: - resource_name = 'requiredfknotes' - queryset = NoteWithEditor.objects.all() - authorization = Authorization() - - -class ThrottledNoteResource(NoteResource): - class Meta: - resource_name = 'throttlednotes' - queryset = Note.objects.filter(is_active=True) - throttle = CacheThrottle(throttle_at=2, timeframe=5, expiration=5) - authorization = Authorization() - - -class BasicAuthNoteResource(NoteResource): - class Meta: - resource_name = 'notes' - queryset = Note.objects.filter(is_active=True) - authentication = BasicAuthentication() - authorization = Authorization() - - -class NoUriNoteResource(ModelResource): - class Meta: - queryset = Note.objects.filter(is_active=True) - include_resource_uri = False - authorization = Authorization() - - -class WithAbsoluteURLNoteResource(ModelResource): - class Meta: - queryset = Note.objects.filter(is_active=True) - include_absolute_url = True - resource_name = 'withabsoluteurlnote' - authorization = Authorization() - - def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'): - if bundle_or_obj is None: - return '/api/v1/withabsoluteurlnote/' - - return '/api/v1/withabsoluteurlnote/%s/' % bundle_or_obj.obj.id - - -class AlternativeCollectionNameNoteResource(ModelResource): - class Meta: - queryset = Note.objects.filter(is_active=True) - collection_name = 'alt_objects' - authorization = Authorization() - - -class SubjectResource(ModelResource): - class Meta: - queryset = Subject.objects.all() - resource_name = 'subjects' - filtering = { - 'name': ALL, - } - authorization = Authorization() - - -class RelatedNoteResource(ModelResource): - author = fields.ForeignKey(UserResource, 'author', verbose_name='The Author') - subjects = fields.ManyToManyField(SubjectResource, 'subjects') - - class Meta: - queryset = Note.objects.all() - resource_name = 'relatednotes' - filtering = { - 'author': ALL, - 'subjects': ALL_WITH_RELATIONS, - } - fields = ['title', 'slug', 'content', 'created', 'is_active'] - authorization = Authorization() - - -class AnotherSubjectResource(ModelResource): - notes = fields.ToManyField(DetailedNoteResource, 'notes') - - class Meta: - queryset = Subject.objects.all() - resource_name = 'anothersubjects' - excludes = ['notes'] - filtering = { - 'notes': ALL_WITH_RELATIONS, - } - authorization = Authorization() - - -class AnotherRelatedNoteResource(ModelResource): - author = fields.ForeignKey(UserResource, 'author') - subjects = fields.ManyToManyField(SubjectResource, 'subjects', full=True) - - class Meta: - queryset = Note.objects.all() - resource_name = 'relatednotes' - filtering = { - 'author': ALL, - 'subjects': ALL_WITH_RELATIONS, - } - fields = ['title', 'slug', 'content', 'created', 'is_active'] - authorization = Authorization() - - -class YetAnotherRelatedNoteResource(ModelResource): - author = fields.ForeignKey(UserResource, 'author', full=True) - subjects = fields.ManyToManyField(SubjectResource, 'subjects') - - class Meta: - queryset = Note.objects.all() - resource_name = 'relatednotes' - filtering = { - 'author': ALL, - 'subjects': ALL_WITH_RELATIONS, - } - fields = ['title', 'slug', 'content', 'created', 'is_active'] - authorization = Authorization() - - -class NullableRelatedNoteResource(AnotherRelatedNoteResource): - author = fields.ForeignKey(UserResource, 'author', null=True) - subjects = fields.ManyToManyField(SubjectResource, 'subjects', null=True) - - -class NullableMediaBitResource(ModelResource): - # The old (broke) way to allow ``note`` to be omitted, even though it's a required field. - note = fields.ToOneField(NoteResource, 'note', null=True) - - class Meta: - queryset = MediaBit.objects.all() - resource_name = 'nullablemediabit' - authorization = Authorization() - - -class ReadOnlyRelatedNoteResource(ModelResource): - author = fields.ToOneField(UserResource, 'author', readonly=True) - my_property = fields.CharField(attribute='my_property', null=True, readonly=True) - - class Meta: - queryset = Note.objects.all() - authorization = Authorization() - - -class BlankMediaBitResource(ModelResource): - # Allow ``note`` to be omitted, even though it's a required field. - note = fields.ToOneField(NoteResource, 'note', blank=True) - - class Meta: - queryset = MediaBit.objects.all() - resource_name = 'blankmediabit' - authorization = Authorization() - - # We'll custom populate the note here if it's not present. - # Doesn't make a ton of sense in this context, but for things - # like ``user`` or ``site`` that you can autopopulate based - # on the request. - def hydrate_note(self, bundle): - if not bundle.data.get('note'): - bundle.obj.note = Note.objects.get(pk=1) - - return bundle - - -class TestOptionsResource(ModelResource): - class Meta: - queryset = Note.objects.all() - allowed_methods = ['post'] - list_allowed_methods = ['post', 'put'] - authorization = Authorization() - - -# Per user authorization bits. -class PerUserAuthorization(Authorization): - def read_list(self, object_list, bundle): - if bundle.request and hasattr(bundle.request, 'user'): - if bundle.request.user.is_authenticated(): - object_list = object_list.filter(author=bundle.request.user) - else: - object_list = object_list.none() - - return object_list - - -class PerUserNoteResource(NoteResource): - class Meta: - resource_name = 'perusernotes' - queryset = Note.objects.all() - authorization = PerUserAuthorization() - - def authorized_read_list(self, object_list, bundle): - if object_list._result_cache is not None: - self._pre_limits = len(object_list._result_cache) - else: - self._pre_limits = 0 - - # Just to demonstrate the per-resource hooks. - new_object_list = super(PerUserNoteResource, self).authorized_read_list(object_list, bundle) - - if object_list._result_cache is not None: - self._post_limits = len(object_list._result_cache) - else: - self._post_limits = 0 - - return new_object_list.filter(is_active=True) -# End per user authorization bits. - - -# Per object authorization bits. -class PerObjectAuthorization(Authorization): - def read_list(self, object_list, bundle): - # Does a per-object check that "can't" be expressed as part of a - # ``QuerySet``. This helps test that all objects in the ``QuerySet`` - # aren't loaded & evaluated, only results that match the request. - final_list = [] - - for obj in object_list: - # Only match ``Note`` objects with 'post' in the title. - if 'post' in obj.title.lower(): - final_list.append(obj) - - return final_list - - -class PerObjectNoteResource(NoteResource): - class Meta: - resource_name = 'perobjectnotes' - queryset = Note.objects.all() - authorization = PerObjectAuthorization() - filtering = { - 'is_active': ALL, - } - - def authorized_read_list(self, object_list, bundle): - if object_list._result_cache is not None: - self._pre_limits = len(object_list._result_cache) - else: - self._pre_limits = 0 - - # Check the QuerySet cache to make sure we haven't populated everything. - new_object_list = super(PerObjectNoteResource, self).authorized_read_list(object_list, bundle) - - self._post_limits = len(object_list._result_cache) - return new_object_list -# End per object authorization bits. - - -class CounterResource(ModelResource): - count = fields.IntegerField('count', default=0, null=True) - - class Meta: - queryset = Counter.objects.all() - authorization = Authorization() - - def full_hydrate(self, bundle): - bundle.times_hydrated = getattr(bundle, 'times_hydrated', 0) + 1 - new_shiny = super(CounterResource, self).full_hydrate(bundle) - new_shiny.obj.count = new_shiny.times_hydrated - return new_shiny - - -class CounterAuthorization(Authorization): - def create_detail(self, object_list, bundle, *args, **kwargs): - bundle._create_auth_call_count = getattr(bundle, '_create_auth_call_count', 0) + 1 - return True - - def update_detail(self, object_list, bundle, *args, **kwargs): - bundle._update_auth_call_count = getattr(bundle, '_update_auth_call_count', 0) + 1 - return True - - -class CounterCreateDetailResource(ModelResource): - count = fields.IntegerField('count', default=0, null=True) - - class Meta: - queryset = Counter.objects.all() - authorization = CounterAuthorization() - - -class CounterUpdateDetailResource(ModelResource): - count = fields.IntegerField('count', default=0, null=True) - - class Meta: - queryset = Counter.objects.all() - authorization = CounterAuthorization() - - -class ModelResourceTestCase(TestCase): - fixtures = ['note_testdata.json'] - urls = 'core.tests.resource_urls' - - def setUp(self): - super(ModelResourceTestCase, self).setUp() - self.note_1 = Note.objects.get(pk=1) - self.subject_1 = Subject.objects.create( - name='News', - url='/news/' - ) - self.subject_2 = Subject.objects.create( - name='Photos', - url='/photos/' - ) - self.note_1.subjects.add(self.subject_1) - self.note_1.subjects.add(self.subject_2) - - def tearDown(self): - cache.clear() - - @patch('django.core.signals.got_request_exception.send') - @patch('tastypie.resources.ModelResource.obj_get_list', side_effect=IOError) - def test_exception_handling(self, obj_get_list_mock, send_signal_mock): - request = HttpRequest() - request.method = 'GET' - resource = NoteResource() - resource.wrap_view('dispatch_list')(request) - self.assertTrue(obj_get_list_mock.called, msg="Test invalid: obj_get_list should have been dispatched") - self.assertTrue(send_signal_mock.called, msg="got_request_exception was not called after an error") - - def test_escaping(self): - request = HttpRequest() - request.method = 'GET' - request.GET = { - 'limit': '', - } - resource = NoteResource() - res = resource.wrap_view('dispatch_list')(request) - self.assertEqual(res.status_code, 400) - err_data = json.loads(res.content.decode('utf-8')) - self.assertTrue('<script>alert(1)</script>' in err_data['error']) - - def test_init(self): - # Very minimal & stock. - resource_1 = NoteResource() - self.assertEqual(len(resource_1.fields), 8) - self.assertNotEqual(resource_1._meta.queryset, None) - self.assertEqual(resource_1._meta.resource_name, 'notes') - self.assertEqual(resource_1._meta.limit, 20) - self.assertEqual(resource_1._meta.list_allowed_methods, ['get', 'post', 'put', 'delete', 'patch']) - self.assertEqual(resource_1._meta.detail_allowed_methods, ['get', 'post', 'put', 'delete', 'patch']) - self.assertEqual(isinstance(resource_1._meta.serializer, Serializer), True) - - # Lightly custom. - resource_2 = LightlyCustomNoteResource() - self.assertEqual(len(resource_2.fields), 8) - self.assertNotEqual(resource_2._meta.queryset, None) - self.assertEqual(resource_2._meta.resource_name, 'noteish') - self.assertEqual(resource_2._meta.limit, 20) - self.assertEqual(resource_2._meta.list_allowed_methods, ['get']) - self.assertEqual(resource_2._meta.detail_allowed_methods, ['get']) - self.assertEqual(isinstance(resource_2._meta.serializer, Serializer), True) - - # Highly custom. - resource_3 = VeryCustomNoteResource() - self.assertEqual(len(resource_3.fields), 7) - self.assertNotEqual(resource_3._meta.queryset, None) - self.assertEqual(resource_3._meta.resource_name, 'notey') - self.assertEqual(resource_3._meta.limit, 50) - self.assertEqual(resource_3._meta.list_allowed_methods, ['get']) - self.assertEqual(resource_3._meta.detail_allowed_methods, ['get', 'post', 'put']) - self.assertEqual(isinstance(resource_3._meta.serializer, CustomSerializer), True) - - # Note - automatic resource naming. - resource_4 = NoUriNoteResource() - self.assertEqual(resource_4._meta.resource_name, 'nourinote') - - # Test to make sure that, even with a mix of basic & advanced - # configuration, options are set right. - resource_5 = TestOptionsResource() - self.assertEqual(resource_5._meta.allowed_methods, ['post']) - # Should be the overridden values. - self.assertEqual(resource_5._meta.list_allowed_methods, ['post', 'put']) - # Should inherit from the basic configuration. - self.assertEqual(resource_5._meta.detail_allowed_methods, ['post']) - - resource_6 = CustomPageNoteResource() - self.assertEqual(resource_6._meta.paginator_class, CustomPaginator) - - def test_can_create(self): - resource_1 = NoteResource() - self.assertEqual(resource_1.can_create(), True) - - resource_2 = LightlyCustomNoteResource() - self.assertEqual(resource_2.can_create(), False) - - def test_can_update(self): - resource_1 = NoteResource() - self.assertEqual(resource_1.can_update(), True) - - resource_2 = LightlyCustomNoteResource() - self.assertEqual(resource_2.can_update(), False) - - resource_3 = TestOptionsResource() - self.assertEqual(resource_3.can_update(), True) - - def test_can_delete(self): - resource_1 = NoteResource() - self.assertEqual(resource_1.can_delete(), True) - - resource_2 = LightlyCustomNoteResource() - self.assertEqual(resource_2.can_delete(), False) - - def test_fields(self): - # Different from the ``ResourceTestCase.test_fields``, we're checking - # some related bits here & self-referential bits later on. - resource_1 = RelatedNoteResource() - self.assertEqual(len(resource_1.fields), 8) - self.assert_('author' in resource_1.fields) - self.assertTrue(isinstance(resource_1.fields['author'], fields.ToOneField)) - self.assertEqual(resource_1.fields['author']._resource, resource_1.__class__) - self.assertEqual(resource_1.fields['author'].instance_name, 'author') - self.assertTrue('subjects' in resource_1.fields) - self.assertTrue(isinstance(resource_1.fields['subjects'], fields.ToManyField)) - self.assertEqual(resource_1.fields['subjects']._resource, resource_1.__class__) - self.assertEqual(resource_1.fields['subjects'].instance_name, 'subjects') - - # Sanity check the other introspected fields. - annr = AutoNowNoteResource() - self.assertEqual(len(annr.fields), 8) - self.assertEqual(sorted(annr.fields.keys()), ['content', 'created', 'id', 'is_active', 'resource_uri', 'slug', 'title', 'updated']) - - self.assertTrue(isinstance(annr.fields['content'], fields.CharField)) - self.assertEqual(annr.fields['content'].attribute, 'content') - self.assertEqual(annr.fields['content'].blank, True) - self.assertEqual(annr.fields['content']._default, '') - self.assertEqual(annr.fields['content'].instance_name, 'content') - self.assertEqual(annr.fields['content'].null, False) - self.assertEqual(annr.fields['content'].readonly, False) - self.assertEqual(annr.fields['content'].unique, False) - - self.assertTrue(isinstance(annr.fields['created'], fields.DateTimeField)) - self.assertEqual(annr.fields['created'].attribute, 'created') - self.assertEqual(annr.fields['created'].blank, False) - self.assertTrue(isinstance(annr.fields['created']._default(), datetime.datetime)) - self.assertEqual(annr.fields['created'].instance_name, 'created') - self.assertEqual(annr.fields['created'].null, True) - self.assertEqual(annr.fields['created'].readonly, False) - self.assertEqual(annr.fields['created'].unique, False) - - self.assertTrue(isinstance(annr.fields['id'], fields.IntegerField)) - self.assertEqual(annr.fields['id'].attribute, 'id') - self.assertEqual(annr.fields['id'].blank, True) - self.assertEqual(annr.fields['id']._default, '') - self.assertEqual(annr.fields['id'].instance_name, 'id') - self.assertEqual(annr.fields['id'].null, False) - self.assertEqual(annr.fields['id'].readonly, False) - self.assertEqual(annr.fields['id'].unique, True) - - self.assertTrue(isinstance(annr.fields['is_active'], fields.BooleanField)) - self.assertEqual(annr.fields['is_active'].attribute, 'is_active') - self.assertEqual(annr.fields['is_active'].blank, True) - self.assertEqual(annr.fields['is_active']._default, True) - self.assertEqual(annr.fields['is_active'].instance_name, 'is_active') - self.assertEqual(annr.fields['is_active'].null, False) - self.assertEqual(annr.fields['is_active'].readonly, False) - self.assertEqual(annr.fields['is_active'].unique, False) - - self.assertTrue(isinstance(annr.fields['resource_uri'], fields.CharField)) - self.assertEqual(annr.fields['resource_uri'].attribute, None) - self.assertEqual(annr.fields['resource_uri'].blank, False) - self.assertEqual(annr.fields['resource_uri']._default, fields.NOT_PROVIDED) - self.assertEqual(annr.fields['resource_uri'].instance_name, 'resource_uri') - self.assertEqual(annr.fields['resource_uri'].null, False) - self.assertEqual(annr.fields['resource_uri'].readonly, True) - self.assertEqual(annr.fields['resource_uri'].unique, False) - - self.assertTrue(isinstance(annr.fields['slug'], fields.CharField)) - self.assertEqual(annr.fields['slug'].attribute, 'slug') - self.assertEqual(annr.fields['slug'].blank, False) - self.assertEqual(annr.fields['slug']._default, fields.NOT_PROVIDED) - self.assertEqual(annr.fields['slug'].instance_name, 'slug') - self.assertEqual(annr.fields['slug'].null, False) - self.assertEqual(annr.fields['slug'].readonly, False) - self.assertEqual(annr.fields['slug'].unique, True) - - self.assertTrue(isinstance(annr.fields['title'], fields.CharField)) - self.assertEqual(annr.fields['title'].attribute, 'title') - self.assertEqual(annr.fields['title'].blank, False) - self.assertEqual(annr.fields['title']._default, fields.NOT_PROVIDED) - self.assertEqual(annr.fields['title'].instance_name, 'title') - self.assertEqual(annr.fields['title'].null, False) - self.assertEqual(annr.fields['title'].readonly, False) - self.assertEqual(annr.fields['title'].unique, False) - - self.assertTrue(isinstance(annr.fields['updated'], fields.DateTimeField)) - self.assertEqual(annr.fields['updated'].attribute, 'updated') - self.assertEqual(annr.fields['updated'].blank, True) - self.assertTrue(isinstance(annr.fields['updated']._default(), datetime.datetime)) - self.assertEqual(annr.fields['updated'].instance_name, 'updated') - self.assertEqual(annr.fields['updated'].null, False) - self.assertEqual(annr.fields['updated'].readonly, False) - self.assertEqual(annr.fields['updated'].unique, False) - - def test_urls(self): - # The common case, where the ``Api`` specifies the name. - resource = NoteResource(api_name='v1') - patterns = resource.urls - self.assertEqual(len(patterns), 4) - self.assertEqual([pattern.name for pattern in patterns], ['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail']) - self.assertEqual(reverse('api_dispatch_list', kwargs={ - 'api_name': 'v1', - 'resource_name': 'notes', - }), '/api/v1/notes/') - self.assertEqual(reverse('api_dispatch_detail', kwargs={ - 'api_name': 'v1', - 'resource_name': 'notes', - 'pk': 1, - }), '/api/v1/notes/1/') - - # Start over. - resource = NoteResource() - patterns = resource.urls - self.assertEqual(len(patterns), 4) - self.assertEqual([pattern.name for pattern in patterns], ['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail']) - self.assertEqual(reverse('api_dispatch_list', urlconf='core.tests.manual_urls', kwargs={ - 'resource_name': 'notes', - }), '/notes/') - self.assertEqual(reverse('api_dispatch_detail', urlconf='core.tests.manual_urls', kwargs={ - 'resource_name': 'notes', - 'pk': 1, - }), '/notes/1/') - - def test__get_via_uri(self): - resource = NoteResource(api_name='v1') - note_1 = resource.get_via_uri('/api/v1/notes/1/') - self.assertEqual(note_1.pk, 1) - - def test__get_via_uri__app_name_same_as_resource(self): - resource = NoteResource(api_name='v1') - # Should work even if app name is the same as resource - note_1 = resource.get_via_uri('/notes/api/v1/notes/1/') - self.assertEqual(note_1.pk, 1) - - def test__get_via_uri__uri_has_special_chars(self): - resource = NoteResource(api_name='v1') - # Should work even if app name is the same as resource - note_1 = resource.get_via_uri('/~krichy/api/v1/notes/1/') - self.assertEqual(note_1.pk, 1) - - def test__get_via_uri__uri_has_encoded_special_chars(self): - resource = NoteResource(api_name='v1') - # Should work even if app name is the same as resource - note_1 = resource.get_via_uri('/%7ekrichy/api/v1/notes/1/') - self.assertEqual(note_1.pk, 1) - - def test__get_via_uri__invalid_uri(self): - resource = NoteResource(api_name='v1') - with self.assertRaises(NotFound): - resource.get_via_uri('http://example.com/') - - def test__get_via_uri__bad_uri_containing_resource_name(self): - resource = NoteResource(api_name='v1') - with self.assertRaises(NotFound): - resource.get_via_uri('/notes/api/v1/photos/1/') - - def test__get_via_uri__nonexistant_resource(self): - resource = NoteResource(api_name='v1') - with self.assertRaises(NotFound): - resource.get_via_uri('/api/v1/foo/1/') - - def test__get_via_uri__different_resource(self): - resource = NoteResource(api_name='v1') - with self.assertRaises(NotFound): - resource.get_via_uri('/api/v1/photos/1/') - - def test__get_via_uri__list_uri(self): - resource = NoteResource(api_name='v1') - with self.assertRaises(MultipleObjectsReturned): - resource.get_via_uri('/api/v1/notes/') - - def test__get_via_uri__with_request(self): - resource = NoteResource(api_name='v1') - # Check with the request. - request = HttpRequest() - note_1 = resource.get_via_uri('/api/v1/notes/1/', request=request) - self.assertEqual(note_1.pk, 1) - - def test_create_identifier(self): - resource = NoteResource() - new_note = Note.objects.get(pk=1) - self.assertEqual(resource.create_identifier(new_note), 'core.note.1') - - def test_determine_format(self): - resource = NoteResource() - request = HttpRequest() - - # Default. - self.assertEqual(resource.determine_format(request), 'application/json') - - # Test forcing the ``format`` parameter. - request.GET = {'format': 'json'} - self.assertEqual(resource.determine_format(request), 'application/json') - - request.GET = {'format': 'jsonp'} - self.assertEqual(resource.determine_format(request), 'text/javascript') - - request.GET = {'format': 'xml'} - self.assertEqual(resource.determine_format(request), 'application/xml') - - request.GET = {'format': 'yaml'} - self.assertEqual(resource.determine_format(request), 'text/yaml') - - request.GET = {'format': 'foo'} - self.assertEqual(resource.determine_format(request), 'application/json') - - # Test the ``Accept`` header. - request.META = {'HTTP_ACCEPT': 'application/json'} - self.assertEqual(resource.determine_format(request), 'application/json') - - request.META = {'HTTP_ACCEPT': 'text/javascript'} - self.assertEqual(resource.determine_format(request), 'text/javascript') - - request.META = {'HTTP_ACCEPT': 'application/xml'} - self.assertEqual(resource.determine_format(request), 'application/xml') - - request.META = {'HTTP_ACCEPT': 'text/yaml'} - self.assertEqual(resource.determine_format(request), 'text/yaml') - - # unsupported text/html should return default format - request.META = {'HTTP_ACCEPT': 'text/html'} - self.assertEqual(resource.determine_format(request), 'application/json') - - # unsupported applicaiton/octet-stream returns default format - request.META = {'HTTP_ACCEPT': 'application/octet-stream'} - self.assertEqual(resource.determine_format(request), 'application/json') - - request.META = {'HTTP_ACCEPT': 'application/json,application/xml;q=0.9,*/*;q=0.8'} - self.assertEqual(resource.determine_format(request), 'application/json') - - request.META = {'HTTP_ACCEPT': 'text/plain,application/xml,application/json;q=0.9,*/*;q=0.8'} - self.assertEqual(resource.determine_format(request), 'application/xml') - - # your typical browser (chrome, firefoxs, no plugins) should get back xml - request.META = {'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'} - self.assertEqual(resource.determine_format(request), 'application/xml') - - def test_build_schema(self): - related = RelatedNoteResource(api_name='v1') - schema = adjust_schema(related.build_schema()) - expected_schema = { - 'filtering': { - 'subjects': 2, - 'author': 1 - }, - 'allowed_detail_http_methods': ['get', 'post', 'put', 'delete', 'patch'], - 'fields': { - 'author': { - 'related_type': 'to_one', - 'related_schema': '/api/v1/user/schema/', - 'nullable': False, - 'default': 'No default provided.', - 'readonly': False, - 'blank': False, - 'help_text': 'A single related resource. Can be either a URI or set of nested resource data.', - 'verbose_name': 'The Author', - 'unique': False, - 'type': 'related', - "primary_key": False - }, - 'title': { - 'nullable': False, - 'default': 'No default provided.', - 'readonly': False, - 'blank': False, - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'verbose_name': 'The Title', - 'unique': False, - 'type': 'string', - "primary_key": False - }, - 'created': { - 'nullable': False, - 'default': 'The current date.', - 'readonly': False, - 'blank': False, - 'help_text': 'A date & time as a string. Ex: "2010-11-10T03:07:43"', - 'verbose_name': 'created', - 'unique': False, - 'type': 'datetime', - "primary_key": False - }, - 'is_active': { - 'nullable': False, - 'default': True, - 'readonly': False, - 'blank': True, - 'help_text': 'Boolean data. Ex: True', - 'verbose_name': 'is active', - 'unique': False, - 'type': 'boolean', - "primary_key": False - }, - 'content': { - 'nullable': False, - 'default': '', - 'readonly': False, - 'blank': True, - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'verbose_name': 'content', - 'unique': False, - 'type': 'string', - "primary_key": False - }, - 'subjects': { - 'related_type': 'to_many', - 'related_schema': '/api/v1/subjects/schema/', - 'nullable': False, - 'default': 'No default provided.', - 'readonly': False, - 'blank': False, - 'help_text': 'Many related resources. Can be either a list of URIs or list of individually nested resource data.', - 'verbose_name': 'subjects', - 'unique': False, - 'type': 'related', - "primary_key": False - }, - 'slug': { - 'nullable': False, - 'default': 'No default provided.', - 'readonly': False, - 'blank': False, - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'verbose_name': 'slug', - 'unique': False, - 'type': 'string', - "primary_key": False - }, - 'resource_uri': { - 'nullable': False, - 'default': 'No default provided.', - 'readonly': True, - 'blank': False, - 'help_text': 'Unicode string data. Ex: "Hello World"', - 'verbose_name': 'resource uri', - 'unique': False, - 'type': 'string', - "primary_key": False - } - }, - 'default_format': 'application/json', - 'default_limit': 20, - 'allowed_list_http_methods': ['get', 'post', 'put', 'delete', 'patch'] - } - self.assertEqual(schema, expected_schema) - - def test_build_filters(self): - resource = NoteResource() - - # Valid none. - self.assertEqual(resource.build_filters(), {}) - self.assertEqual(resource.build_filters(filters=None), {}) - - # Not in the filtering dict. - self.assertEqual(resource.build_filters(filters={'resource_url__exact': '/foo/bar/'}), {}) - - # Filter valid but disallowed. - self.assertRaises(InvalidFilterError, resource.build_filters, filters={'slug__startswith': 'whee'}) - - # Skipped due to not being recognized. - self.assertEqual(resource.build_filters(filters={'moof__exact': 'baz'}), {}) - - # Invalid simple (implicit ``__exact``). - self.assertEqual(resource.build_filters(filters={'title': 'Hello world.'}), {'title__exact': 'Hello world.'}) - - # Valid simple (explicit ``__exact``). - self.assertEqual(resource.build_filters(filters={'title__exact': 'Hello world.'}), {'title__exact': 'Hello world.'}) - - # Valid in (using ``,``). - self.assertEqual(resource.build_filters(filters={'title__in': ''}), {'title__in': ''}) - self.assertEqual(resource.build_filters(filters={'title__in': 'foo'}), {'title__in': ['foo']}) - self.assertEqual(resource.build_filters(filters={'title__in': 'foo,bar'}), {'title__in': ['foo', 'bar']}) - - # Valid in (using multiple params). - self.assertEqual(resource.build_filters(filters=QueryDict('title__in=foo&title__in=bar')), {'title__in': ['foo', 'bar']}) - self.assertEqual(resource.build_filters(filters=QueryDict('title__in=foo,bar')), {'title__in': ['foo', 'bar']}) - - # Valid simple (non-``__exact``). - self.assertEqual(resource.build_filters(filters={'content__startswith': 'Hello'}), {'content__startswith': 'Hello'}) - - # Valid boolean. - self.assertEqual(resource.build_filters(filters={'title': 'true'}), {'title__exact': True}) - self.assertEqual(resource.build_filters(filters={'title': 'True'}), {'title__exact': True}) - self.assertEqual(resource.build_filters(filters={'title': True}), {'title__exact': True}) - self.assertEqual(resource.build_filters(filters={'title': 'false'}), {'title__exact': False}) - self.assertEqual(resource.build_filters(filters={'title': 'False'}), {'title__exact': False}) - self.assertEqual(resource.build_filters(filters={'title': False}), {'title__exact': False}) - self.assertEqual(resource.build_filters(filters={'title': 'nil'}), {'title__exact': None}) - self.assertEqual(resource.build_filters(filters={'title': 'none'}), {'title__exact': None}) - self.assertEqual(resource.build_filters(filters={'title': 'None'}), {'title__exact': None}) - self.assertEqual(resource.build_filters(filters={'title': None}), {'title__exact': None}) - - # Valid multiple. - self.assertEqual(resource.build_filters(filters={ - 'slug__exact': 'Hello', - 'title__exact': 'RAGE', - 'content__startswith': 'A thing here.' - }), {'slug__exact': 'Hello', 'content__startswith': 'A thing here.', 'title__exact': 'RAGE'}) - - # Valid multiple (model attribute differs from field name). - resource_2 = DetailedNoteResource() - filters_1 = { - 'slug__exact': 'Hello', - 'title__exact': 'RAGE', - 'content__startswith': 'A thing here.', - 'user__gt': 2, - } - self.assertEqual(resource_2.build_filters(filters=filters_1), {'title__exact': 'RAGE', 'slug__exact': 'Hello', 'author__gt': 2, 'content__startswith': 'A thing here.'}) - - # No relationship traversal to the filter, please. - resource_3 = RelatedNoteResource() - self.assertRaises(InvalidFilterError, resource_3.build_filters, filters={'author__username__startswith': 'j'}) - - # Allow relationship traversal. - self.assertEqual(resource_3.build_filters(filters={'subjects__name__startswith': 'News'}), {'subjects__name__startswith': 'News'}) - - # Ensure related fields that do not have filtering throw an exception. - self.assertRaises(InvalidFilterError, resource_3.build_filters, filters={'subjects__url__startswith': 'News'}) - - # Ensure related fields that do not exist throw an exception. - self.assertRaises(InvalidFilterError, resource_3.build_filters, filters={'subjects__foo__startswith': 'News'}) - - # Check where the field name doesn't match the database relation. - resource_4 = AnotherSubjectResource() - self.assertEqual(resource_4.build_filters(filters={'notes__user__startswith': 'Daniel'}), {'notes__author__startswith': 'Daniel'}) - - # Make sure that fields that don't have attributes can't be filtered on. - self.assertRaises(InvalidFilterError, resource_4.build_filters, filters={'notes__hello_world': 'News'}) - - # Make sure build_filters works even on resources without queryset - resource = NoQuerysetNoteResource() - self.assertEqual(resource.build_filters(), {}) - - def test_xss_regressions(self): - # Make sure the body is JSON & the content-type is right. - resource = RelatedNoteResource() - request = HttpRequest() - request.method = 'GET' - - request.GET = { - 'format': 'xml', - 'author__username__startswith': 'j', - } - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp['content-type'], 'application/xml; charset=utf-8') - self.assertEqual(resp.content.decode('utf-8'), "\nLookups are not allowed more than one level deep on the 'author' field.") - - request.GET = { - 'format': 'json', - 'author__': 'j', - } - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp['content-type'], 'application/json') - self.assertEqual(resp.content.decode('utf-8'), '{"error": "Lookups are not allowed more than one level deep on the \'author\' field."}') - - request.GET = { - 'format': 'json', - 'limit': '', - } - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp['content-type'], 'application/json') - self.assertEqual(resp.content.decode('utf-8'), '{"error": "Invalid limit \'<img%20src=\\"http://ycombinator.com/images/y18.gif\\">\' provided. Please provide a positive integer."}') - - request.GET = { - 'format': 'json', - 'limit': '', - } - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp['content-type'], 'application/json') - self.assertEqual(resp.content.decode('utf-8'), '{"error": "Invalid limit \'<img%20src=\\"http://ycombinator.com/images/y18.gif\\">\' provided. Please provide a positive integer."}') - - request.GET = { - 'format': 'json', - 'offset': '', - } - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp['content-type'], 'application/json') - self.assertEqual(resp.content.decode('utf-8'), '{"error": "Invalid offset \'<script>alert(\\"XSS\\")</script>\' provided. Please provide an integer."}') - - def test_apply_sorting(self): - resource = NoteResource() - base_bundle = Bundle() - - # Valid none. - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list) - self.assertEqual([obj.id for obj in ordered_list], [1, 2, 4, 6]) - - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options=None) - self.assertEqual([obj.id for obj in ordered_list], [1, 2, 4, 6]) - - # Not a valid field. - object_list = resource.obj_get_list(base_bundle) - self.assertRaises(InvalidSortError, resource.apply_sorting, object_list, options={'order_by': 'foobar'}) - - # Not in the ordering dict. - object_list = resource.obj_get_list(base_bundle) - self.assertRaises(InvalidSortError, resource.apply_sorting, object_list, options={'order_by': 'content'}) - - # No attribute to sort by. - object_list = resource.obj_get_list(base_bundle) - self.assertRaises(InvalidSortError, resource.apply_sorting, object_list, options={'order_by': 'resource_uri'}) - - # Valid ascending. - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options={'order_by': 'title'}) - self.assertEqual([obj.id for obj in ordered_list], [2, 1, 6, 4]) - - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options={'order_by': 'slug'}) - self.assertEqual([obj.id for obj in ordered_list], [2, 1, 6, 4]) - - # Valid descending. - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options={'order_by': '-title'}) - self.assertEqual([obj.id for obj in ordered_list], [4, 6, 1, 2]) - - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options={'order_by': '-slug'}) - self.assertEqual([obj.id for obj in ordered_list], [4, 6, 1, 2]) - - # Ensure the deprecated parameter still works. - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options={'sort_by': '-title'}) - self.assertEqual([obj.id for obj in ordered_list], [4, 6, 1, 2]) - - # Valid combination. - object_list = resource.obj_get_list(base_bundle) - ordered_list = resource.apply_sorting(object_list, options={'order_by': ['title', '-slug']}) - self.assertEqual([obj.id for obj in ordered_list], [2, 1, 6, 4]) - - # Valid (model attribute differs from field name). - resource_2 = DetailedNoteResource(base_bundle) - object_list = resource_2.obj_get_list(base_bundle) - ordered_list = resource_2.apply_sorting(object_list, options={'order_by': '-user'}) - self.assertEqual([obj.id for obj in ordered_list], [6, 4, 2, 1]) - - # Invalid relation. - resource_2 = DetailedNoteResource() - object_list = resource_2.obj_get_list(base_bundle) - ordered_list = resource_2.apply_sorting(object_list, options={'order_by': '-user__baz'}) - - with self.assertRaises(FieldError): - [obj.id for obj in ordered_list] - - # Valid relation. - resource_2 = DetailedNoteResource() - object_list = resource_2.obj_get_list(base_bundle) - ordered_list = resource_2.apply_sorting(object_list, options={'order_by': 'user__id'}) - self.assertEqual([obj.id for obj in ordered_list], [1, 2, 4, 6]) - - resource_2 = DetailedNoteResource() - object_list = resource_2.obj_get_list(base_bundle) - ordered_list = resource_2.apply_sorting(object_list, options={'order_by': '-user__id'}) - self.assertEqual([obj.id for obj in ordered_list], [6, 4, 2, 1]) - - # Valid relational combination. - resource_2 = DetailedNoteResource() - object_list = resource_2.obj_get_list(base_bundle) - ordered_list = resource_2.apply_sorting(object_list, options={'order_by': ['-user__username', 'title']}) - self.assertEqual([obj.id for obj in ordered_list], [2, 1, 6, 4]) - - def test_get_list(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 4}, "objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": 4, "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": 6, "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}]}') - - # Test slicing. - # First an invalid offset. - request.GET = {'format': 'json', 'offset': 'abc', 'limit': 1} - with self.assertRaises(BadRequest): - resp = resource.get_list(request) - - # Try again with ``wrap_view`` for sanity. - resp = resource.wrap_view('get_list')(request) - self.assertEqual(resp.status_code, 400) - - # Then an out of range offset. - request.GET = {'format': 'json', 'offset': -1, 'limit': 1} - with self.assertRaises(BadRequest): - resp = resource.get_list(request) - - # Then an out of range limit. - request.GET = {'format': 'json', 'offset': 0, 'limit': -1} - with self.assertRaises(BadRequest): - resp = resource.get_list(request) - - # Valid slice. - request.GET = {'format': 'json', 'offset': 0, 'limit': 2} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 2) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['previous'], None) - self.assertTrue('/api/v1/notes/?' in list_data['meta']['next']) - self.assertTrue('format=json' in list_data['meta']['next']) - self.assertTrue('limit=2' in list_data['meta']['next']) - self.assertTrue('offset=2' in list_data['meta']['next']) - self.assertEqual(list_data['objects'], [ - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - } - ]) - - # Valid, slightly overlapping slice. - request.GET = {'format': 'json', 'offset': 1, 'limit': 2} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 2) - self.assertEqual(list_data['meta']['offset'], 1) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['previous'], None) - self.assertTrue('/api/v1/notes/?' in list_data['meta']['next']) - self.assertTrue('format=json' in list_data['meta']['next']) - self.assertTrue('limit=2' in list_data['meta']['next']) - self.assertTrue('offset=3' in list_data['meta']['next']) - self.assertEqual(list_data['objects'], [ - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - }, - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - } - ]) - - # Valid, non-overlapping slice. - request.GET = {'format': 'json', 'offset': 3, 'limit': 2} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 2) - self.assertEqual(list_data['meta']['offset'], 3) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['next'], None) - self.assertTrue('/api/v1/notes/?' in list_data['meta']['previous']) - self.assertTrue('format=json' in list_data['meta']['previous']) - self.assertTrue('limit=2' in list_data['meta']['previous']) - self.assertTrue('offset=1' in list_data['meta']['previous']) - self.assertEqual(list_data['objects'], [ - { - "content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "created": "2010-04-02T10:05:00", - "id": 6, - "is_active": True, - "resource_uri": "/api/v1/notes/6/", - "slug": "grannys-gone", - "title": "Granny\'s Gone", - "updated": "2010-04-02T10:05:00" - } - ]) - - # Valid, but beyond the bounds slice. - request.GET = {'format': 'json', 'offset': 100, 'limit': 2} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 2) - self.assertEqual(list_data['meta']['offset'], 100) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['next'], None) - self.assertTrue('/api/v1/notes/?' in list_data['meta']['previous']) - self.assertTrue('format=json' in list_data['meta']['previous']) - self.assertTrue('limit=2' in list_data['meta']['previous']) - self.assertTrue('offset=98' in list_data['meta']['previous']) - self.assertEqual(list_data['objects'], []) - - # Valid slice, fetch all results. - request.GET = {'format': 'json', 'offset': 0, 'limit': 0} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 1000) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['previous'], None) - self.assertEqual(list_data['meta']['next'], None) - self.assertEqual(list_data['objects'], [ - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - }, - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - }, - { - "content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "created": "2010-04-02T10:05:00", - "id": 6, - "is_active": True, - "resource_uri": "/api/v1/notes/6/", - "slug": "grannys-gone", - "title": "Granny\'s Gone", - "updated": "2010-04-02T10:05:00" - } - ]) - - # Valid sorting. - request.GET = {'format': 'json', 'order_by': 'title'} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 20) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['previous'], None) - self.assertEqual(list_data['meta']['next'], None) - self.assertEqual(list_data['objects'], [ - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - }, - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "created": "2010-04-02T10:05:00", - "id": 6, - "is_active": True, - "resource_uri": "/api/v1/notes/6/", - "slug": "grannys-gone", - "title": "Granny\'s Gone", - "updated": "2010-04-02T10:05:00" - }, - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - } - ]) - - request.GET = {'format': 'json', 'order_by': '-title'} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 20) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['previous'], None) - self.assertEqual(list_data['meta']['next'], None) - self.assertEqual(list_data['objects'], [ - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - }, - { - "content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "created": "2010-04-02T10:05:00", - "id": 6, - "is_active": True, - "resource_uri": "/api/v1/notes/6/", - "slug": "grannys-gone", - "title": "Granny\'s Gone", - "updated": "2010-04-02T10:05:00" - }, - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - } - ]) - - # invalid sorting - request.GET = {'format': 'json', 'order_by': 'monkey'} - resp = resource.wrap_view('get_list')(request) - self.assertEqual(resp.status_code, 400) - res = json.loads(resp.content.decode('utf-8')) - self.assertTrue('error' in res.keys()) - self.assertTrue('monkey' in res['error']) # Error looks like "No matching \'monkey\' field for ordering on. - - # Test to make sure we're not inadvertently caching the QuerySet. - request.GET = {'format': 'json'} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 20) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 4) - self.assertEqual(list_data['meta']['previous'], None) - self.assertEqual(list_data['meta']['next'], None) - self.assertEqual(list_data['objects'], [ - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - }, - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - }, - { - "content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "created": "2010-04-02T10:05:00", - "id": 6, - "is_active": True, - "resource_uri": "/api/v1/notes/6/", - "slug": "grannys-gone", - "title": "Granny\'s Gone", - "updated": "2010-04-02T10:05:00" - } - ]) - new_note = Note.objects.create( - title='Another fresh note.', - slug='another-fresh-note', - content='Whee!', - created=aware_datetime(2010, 7, 21, 11, 23), - updated=aware_datetime(2010, 7, 21, 11, 23), - ) - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 20) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 5) - self.assertEqual(list_data['meta']['previous'], None) - self.assertEqual(list_data['meta']['next'], None) - self.assertEqual(list_data['objects'], [ - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - }, - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - }, - { - "content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", - "created": "2010-04-02T10:05:00", - "id": 6, - "is_active": True, - "resource_uri": "/api/v1/notes/6/", - "slug": "grannys-gone", - "title": "Granny\'s Gone", - "updated": "2010-04-02T10:05:00" - }, - { - "content": "Whee!", - "created": "2010-07-21T11:23:00", - "id": 7, - "is_active": True, - "resource_uri": "/api/v1/notes/7/", - "slug": "another-fresh-note", - "title": "Another fresh note.", - "updated": make_naive(new_note.updated).isoformat() - } - ]) - - # Regression - Ensure that the limit on the Resource gets used if - # no other limit is requested. - resource = TinyLimitNoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - list_data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(list_data['meta']['limit'], 3) - self.assertEqual(list_data['meta']['offset'], 0) - self.assertEqual(list_data['meta']['total_count'], 5) - self.assertEqual(list_data['meta']['previous'], None) - self.assertTrue('/api/v1/notes/?' in list_data['meta']['next']) - self.assertTrue('format=json' in list_data['meta']['next']) - self.assertTrue('limit=3' in list_data['meta']['next']) - self.assertTrue('offset=3' in list_data['meta']['next']) - self.assertEqual(list_data['objects'], [ - { - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "created": "2010-03-30T20:05:00", - "id": 1, - "is_active": True, - "resource_uri": "/api/v1/notes/1/", - "slug": "first-post", - "title": "First Post!", - "updated": "2010-03-30T20:05:00" - }, - { - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "created": "2010-03-31T20:05:00", - "id": 2, - "is_active": True, - "resource_uri": "/api/v1/notes/2/", - "slug": "another-post", - "title": "Another Post", - "updated": "2010-03-31T20:05:00" - }, - { - "content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", - "created": "2010-04-01T20:05:00", - "id": 4, - "is_active": True, - "resource_uri": "/api/v1/notes/4/", - "slug": "recent-volcanic-activity", - "title": "Recent Volcanic Activity.", - "updated": "2010-04-01T20:05:00" - } - ]) - - def test_get_list_use_in(self): - resource = UseInNoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - resp = resource.get_list(request) - self.assertEqual(resp.status_code, 200) - resp = json.loads(resp.content.decode('utf-8')) - for note in resp['objects']: - self.assertNotIn('content', note) - - def test_get_detail(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - - resp = resource.get_detail(request, pk=1) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - - resp = resource.get_detail(request, pk=2) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}') - - resp = resource.get_detail(request, pk=300) - self.assertEqual(resp.status_code, 404) - - def test_get_detail_use_in(self): - resource = UseInNoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - resp = resource.get_detail(request, pk=1) - self.assertEqual(resp.status_code, 200) - resp = json.loads(resp.content.decode('utf-8')) - self.assertNotIn('title', resp) - - def test_put_list(self): - resource = NoteResource() - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - - self.assertEqual(Note.objects.count(), 6) - request.set_body('{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}]}') - - resp = resource.put_list(request) - self.assertEqual(resp.status_code, 204) - self.assertNotIn('Content-Type', resp) - self.assertEqual(resp.content.decode('utf-8'), '') - self.assertEqual(Note.objects.count(), 3) - self.assertEqual(Note.objects.filter(is_active=True).count(), 1) - new_note = Note.objects.get(slug='cat-is-back-again') - self.assertEqual(new_note.content, "The cat is back. The dog coughed him up out back.") - - always_resource = AlwaysDataNoteResource() - resp = always_resource.put_list(request) - self.assertEqual(resp.status_code, 200) - self.assertTrue(resp.content.decode('utf-8').startswith('{"objects": [')) - - def test_put_list_with_use_in(self): - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - - self.assertEqual(Note.objects.count(), 6) - request.set_body('{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}]}') - - always_resource = AlwaysDataNoteResourceUseIn() - resp = always_resource.put_list(request) - self.assertEqual(resp.status_code, 200) - content = json.loads(resp.content.decode('utf-8')) - self.assertTrue(len(content['objects']) == 1) - for note in content['objects']: - self.assertIn('constant', note) - self.assertNotIn('author', note) - - def test_put_detail(self): - self.assertEqual(Note.objects.count(), 6) - resource = NoteResource() - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00", "baddata": "some data"}') - - resp = resource.put_detail(request, pk=10) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Note.objects.count(), 7) - new_note = Note.objects.get(slug='cat-is-back') - self.assertEqual(new_note.content, "The cat is back. The dog coughed him up out back.") - - request.set_body('{"content": "The cat is gone again. I think it was the rabbits that ate him this time.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back", "title": "The Cat Is Gone", "updated": "2010-04-03 20:05:00"}') - - resp = resource.put_detail(request, pk=10) - self.assertEqual(resp.status_code, 204) - self.assertNotIn('Content-Type', resp) - self.assertEqual(Note.objects.count(), 7) - new_note = Note.objects.get(slug='cat-is-back') - self.assertEqual(new_note.content, u'The cat is gone again. I think it was the rabbits that ate him this time.') - - always_resource = AlwaysDataNoteResource() - resp = always_resource.put_detail(request, pk=10) - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertTrue("id" in data) - self.assertEqual(data["id"], 10) - self.assertTrue("content" in data) - self.assertEqual(data["content"], "The cat is gone again. I think it was the rabbits that ate him this time.") - self.assertTrue("resource_uri" in data) - self.assertTrue("title" in data) - self.assertTrue("is_active" in data) - self.assertFalse("baddata" in data) - - # Now make sure we can null-out a relation. - # Associate some data first. - new_note = Note.objects.get(slug='cat-is-back') - new_note.author = User.objects.get(username='johndoe') - new_note.save() - nullable_resource = NullableRelatedNoteResource() - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00", "author": null}') - - resp = nullable_resource.put_detail(request, pk=10) - self.assertEqual(resp.status_code, 204) - self.assertNotIn('Content-Type', resp) - self.assertEqual(Note.objects.count(), 7) - new_note = Note.objects.get(slug='cat-is-back') - self.assertEqual(new_note.author, None) - - def test_put_detail_with_use_in(self): - new_note = Note.objects.get(slug='another-post') - new_note.author = User.objects.get(username='johndoe') - new_note.save() - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}') - - always_resource = AlwaysDataNoteResourceUseIn() - resp = always_resource.put_detail(request, pk=new_note.pk) - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertTrue("id" in data) - self.assertEqual(data["id"], new_note.pk) - self.assertTrue("author" in data) - self.assertFalse("constant" in data) - self.assertTrue("resource_uri" in data) - self.assertTrue("title" in data) - self.assertTrue("is_active" in data) - - def test_put_detail_with_identifiers(self): - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"date": "2012-09-07", "username": "WAT", "message": "hello"}') - - date_record_resource = DateRecordResource() - resp = date_record_resource.put_detail(request, username="maraujop") - - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(data['username'], "MARAUJOP") - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"date": "WAT", "username": "maraujop", "message": "hello"}') - - date_record_resource = DateRecordResource() - resp = date_record_resource.put_detail(request, date="2012-09-07") - - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(data['date'], "2012-09-07") - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"date": "2012-09-07", "username": "maraujop", "message": "WAT"}') - date_record_resource = DateRecordResource() - resp = date_record_resource.put_detail(request, message="HELLO") - - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(data['message'], "hello") - - def test_put_detail_with_non_unique_detail_uri_name(self): - """ - Make sure when something besides 'pk' is used for detail_uri_name and - it isn't unique that we still look up the correct object and don't - create a duplicate. - """ - self.assertFalse(Note._meta.get_field('slug').unique) - - new_note = Note.objects.get(slug='another-post') - new_note.author = User.objects.get(username='johndoe') - new_note.save() - - self.assertEqual(Note.objects.count(), 6) - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}') - - resource = NoteResourceNonUniqueDetailUriName() - resp = resource.put_detail(request, slug=new_note.slug) - - self.assertEqual(resp.status_code, 200) - - self.assertEqual(Note.objects.count(), 6) - - data = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(data["id"], new_note.pk) - self.assertEqual(data["slug"], new_note.slug) - self.assertTrue("author" in data) - self.assertFalse("constant" in data) - self.assertTrue("resource_uri" in data) - self.assertTrue("title" in data) - self.assertTrue("is_active" in data) - - @skipIf(MyUUIDModel is None, 'UUIDField not available') - def test_put_detail_with_non_unique_uuid_detail_uri_name(self): - """ - Make sure when something besides 'pk' is used for detail_uri_name and - it isn't unique that we still look up the correct object and don't - create a duplicate. - """ - self.assertFalse(MyUUIDModel._meta.get_field('anotheruuid').unique) - - myuuidmodel = MyUUIDModel.objects.create() - - self.assertEqual(MyUUIDModel.objects.count(), 1) - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"content": "The cat is back. The dog coughed him up out back."}') - - resource = MyUUIDModelResourceNonUniqueDetailUriName() - resp = resource.put_detail(request, anotheruuid=myuuidmodel.anotheruuid) - - self.assertEqual(resp.status_code, 200) - - self.assertEqual(MyUUIDModel.objects.count(), 1) - - data = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(data['id'], str(myuuidmodel.pk)) - self.assertEqual(data['anotheruuid'], str(myuuidmodel.anotheruuid)) - self.assertNotEqual(data['content'], '') - - @skipIf(MyUUIDModel is None, 'UUIDField not available') - def test_patch_detail_with_m2m_non_unique_uuid_detail_uri_name(self): - """ - Make sure when something besides 'pk' is used for detail_uri_name and - it isn't unique that we still look up the correct object and don't - create a duplicate. - Make sure this still works when used as a related resource. - """ - self.assertFalse(MyUUIDModel._meta.get_field('anotheruuid').unique) - - myuuidmodel = MyUUIDModel.objects.create() - myrelateduuidmodel = MyRelatedUUIDModel.objects.create() - myrelateduuidmodel.myuuidmodels.add(myuuidmodel) - - self.assertEqual(MyUUIDModel.objects.count(), 1) - self.assertEqual(MyRelatedUUIDModel.objects.count(), 1) - - data = { - 'myuuidmodels': [ - { - 'content': 'foo', - 'resource_uri': MyUUIDModelResourceNonUniqueDetailUriName().get_resource_uri(myuuidmodel) - }, - { - 'content': 'bar', - 'order': 1, - } - ] - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body(json.dumps(data)) - - self.assertNotEqual(myuuidmodel.content, data['myuuidmodels'][0]['content']) - - resource = MyRelatedUUIDModelResource() - resp = resource.patch_detail(request, pk=myrelateduuidmodel.pk) - - self.assertEqual(resp.status_code, 202) - - self.assertEqual(MyUUIDModel.objects.count(), 2) - self.assertEqual(MyRelatedUUIDModel.objects.count(), 1) - - myuuidmodel = MyUUIDModel.objects.get(pk=myuuidmodel.pk) - - self.assertEqual(myuuidmodel.content, data['myuuidmodels'][0]['content']) - - resp_data = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(resp_data['myuuidmodels'][0]['id'], str(myuuidmodel.pk)) - self.assertEqual(resp_data['myuuidmodels'][0]['anotheruuid'], str(myuuidmodel.anotheruuid)) - self.assertEqual(resp_data['myuuidmodels'][0]['content'], data['myuuidmodels'][0]['content']) - self.assertEqual(resp_data['myuuidmodels'][1]['content'], data['myuuidmodels'][1]['content']) - - def test_put_detail_with_default_pk(self): - obj = MyDefaultPKModel.objects.create() - - self.assertEqual(MyDefaultPKModel.objects.count(), 1) - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"content": "The cat is back. The dog coughed him up out back."}') - - resource = MyDefaultPKModelResource() - resp = resource.put_detail(request, pk=obj.pk) - - self.assertEqual(resp.status_code, 200) - - self.assertEqual(MyDefaultPKModel.objects.count(), 1) - - data = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(data["id"], obj.pk) - self.assertNotEqual(data["content"], '') - - def test_post_list(self): - self.assertEqual(Note.objects.count(), 6) - resource = NoteResource() - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}') - - resp = resource.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Note.objects.count(), 7) - new_note = Note.objects.get(slug='cat-is-back') - self.assertEqual(new_note.content, "The cat is back. The dog coughed him up out back.") - - always_resource = AlwaysDataNoteResource() - resp = always_resource.post_list(request) - self.assertEqual(resp.status_code, 201) - data = json.loads(resp.content.decode('utf-8')) - self.assertTrue("id" in data) - self.assertEqual(data["id"], 8) - self.assertTrue("content" in data) - self.assertEqual(data["content"], "The cat is back. The dog coughed him up out back.") - self.assertTrue("resource_uri" in data) - self.assertTrue("title" in data) - self.assertTrue("is_active" in data) - - def test_post_detail(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - - resp = resource.post_detail(request, pk=2) - self.assertEqual(resp.status_code, 501) - - def test_delete_list(self): - self.assertEqual(Note.objects.count(), 6) - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'DELETE' - - resp = resource.delete_list(request) - self.assertEqual(resp.status_code, 204) - self.assertNotIn('Content-Type', resp) - # Only the non-actives are left alive. - self.assertEqual(Note.objects.count(), 2) - - def test_delete_detail(self): - self.assertEqual(Note.objects.count(), 6) - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'DELETE' - - resp = resource.delete_detail(request, pk=2) - self.assertEqual(resp.status_code, 204) - self.assertNotIn('Content-Type', resp) - self.assertEqual(Note.objects.count(), 5) - - def test_patch_list(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False - - self.assertEqual(Note.objects.count(), 6) - request._raw_post_data = request._body = '{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}, {"resource_uri": "/api/v1/notes/2/", "content": "This is note 2."}], "deleted_objects": ["/api/v1/notes/1/"]}' - - resp = resource.patch_list(request) - self.assertEqual(resp.status_code, 202) - self.assertEqual(resp.content.decode('utf-8'), '') - self.assertEqual(Note.objects.count(), 6) - self.assertEqual(Note.objects.filter(is_active=True).count(), 4) - new_note = Note.objects.get(slug='cat-is-back-again') - self.assertEqual(new_note.content, "The cat is back. The dog coughed him up out back.") - updated_note = Note.objects.get(pk=2) - self.assertEqual(updated_note.content, "This is note 2.") - - def test_patch_list_return_data(self): - always_resource = AlwaysDataNoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False - - self.assertEqual(Note.objects.count(), 6) - request._raw_post_data = request._body = '{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}, {"resource_uri": "/api/v1/alwaysdatanote/2/", "content": "This is note 2."}], "deleted_objects": ["/api/v1/alwaysdatanote/1/"]}' - - resp = always_resource.patch_list(request) - self.assertEqual(resp.status_code, 202) - self.assertTrue(resp.content.decode('utf-8').startswith('{"objects": [')) - - def test_patch_list_return_data_use_in(self): - always_resource = AlwaysDataNoteResourceUseIn() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False - - self.assertEqual(Note.objects.count(), 6) - request._raw_post_data = request._body = '{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}, {"resource_uri": "/api/v1/alwaysdatanote/2/", "content": "This is note 2."}], "deleted_objects": ["/api/v1/alwaysdatanote/1/"]}' - - resp = always_resource.patch_list(request) - self.assertEqual(resp.status_code, 202) - - content = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(len(content['objects']), 2) - for note in content['objects']: - self.assertIn('constant', note) - self.assertNotIn('author', note) - - def test_patch_list_bad_resource_uri(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False - - self.assertEqual(Note.objects.count(), 6) - request._raw_post_data = request._body = '{"objects": [{"resource_uri": "/api/v1/notes/99999/", "content": "This is an invalid resource_uri", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "invalid-uri", "title": "Invalid URI", "updated": "2010-04-03 20:05:00"}]}' - - resp = resource.patch_list(request) - self.assertEqual(resp.status_code, 202) - self.assertEqual(resp.content.decode('utf-8'), '') - self.assertEqual(Note.objects.count(), 7) - new_note = Note.objects.get(slug='invalid-uri') - self.assertEqual(new_note.content, "This is an invalid resource_uri") - - def test_patch_list_with_request_data(self): - """ - Verify that request data is accessible in a Resource's hydrate method after patch_list. - """ - resource = DetailedNoteResourceWithHydrate() - request = HttpRequest() - request.user = User.objects.get(username='johndoe') - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False # Not sure what this line does, copied from above - request._raw_post_data = request._body = '{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-again-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}]}' - - resp = resource.patch_list(request) - self.assertEqual(resp.status_code, 202) - self.assertEqual(resp.content.decode('utf-8'), '') - self.assertEqual(Note.objects.filter(author=request.user, slug="cat-again-again").count(), 1) # Validate that request.user was successfully passed in - - def test_patch_detail(self): - self.assertEqual(Note.objects.count(), 6) - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False - request._raw_post_data = request._body = '{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00"}' - - resp = resource.patch_detail(request, pk=10) - self.assertEqual(resp.status_code, 404) - - resp = resource.patch_detail(request, pk=1) - self.assertEqual(resp.status_code, 202) - self.assertEqual(Note.objects.count(), 6) - note = Note.objects.get(pk=1) - self.assertEqual(note.content, "The cat is back. The dog coughed him up out back.") - self.assertEqual(note.created, aware_datetime(2010, 4, 3, 20, 5)) - - request._raw_post_data = request._body = '{"content": "The cat is gone again. I think it was the rabbits that ate him this time."}' - - resp = resource.patch_detail(request, pk=1) - self.assertEqual(resp.status_code, 202) - self.assertEqual(Note.objects.count(), 6) - new_note = Note.objects.get(pk=1) - self.assertEqual(new_note.content, u'The cat is gone again. I think it was the rabbits that ate him this time.') - - always_resource = AlwaysDataNoteResource() - request._raw_post_data = request._body = '{"content": "Wait, now the cat is back."}' - resp = always_resource.patch_detail(request, pk=1) - self.assertEqual(resp.status_code, 202) - data = json.loads(resp.content.decode('utf-8')) - self.assertTrue("id" in data) - self.assertEqual(data["id"], 1) - self.assertTrue("content" in data) - self.assertEqual(data["content"], u'Wait, now the cat is back.') - self.assertTrue("resource_uri" in data) - self.assertTrue("title" in data) - self.assertTrue("is_active" in data) - - def test_patch_detail_use_in(self): - self.assertEqual(Note.objects.count(), 6) - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request._read_started = False - request._raw_post_data = request._body = '{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00"}' - - resp = resource.patch_detail(request, pk=10) - self.assertEqual(resp.status_code, 404) - - resp = resource.patch_detail(request, pk=1) - self.assertEqual(resp.status_code, 202) - self.assertEqual(Note.objects.count(), 6) - note = Note.objects.get(pk=1) - self.assertEqual(note.content, "The cat is back. The dog coughed him up out back.") - self.assertEqual(note.created, aware_datetime(2010, 4, 3, 20, 5)) - - request._raw_post_data = request._body = '{"content": "The cat is gone again. I think it was the rabbits that ate him this time."}' - - resp = resource.patch_detail(request, pk=1) - self.assertEqual(resp.status_code, 202) - self.assertEqual(Note.objects.count(), 6) - new_note = Note.objects.get(pk=1) - self.assertEqual(new_note.content, u'The cat is gone again. I think it was the rabbits that ate him this time.') - - always_resource = AlwaysDataNoteResourceUseIn() - request._raw_post_data = request._body = '{"content": "Wait, now the cat is back."}' - resp = always_resource.patch_detail(request, pk=1) - self.assertEqual(resp.status_code, 202) - data = json.loads(resp.content.decode('utf-8')) - self.assertTrue("id" in data) - self.assertEqual(data["id"], 1) - self.assertTrue("author" in data) - self.assertFalse("constant" in data) - self.assertTrue("content" in data) - self.assertEqual(data["content"], u'Wait, now the cat is back.') - self.assertTrue("resource_uri" in data) - self.assertTrue("title" in data) - self.assertTrue("is_active" in data) - - def test_dispatch_list(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - resp = resource.dispatch_list(request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 4}, "objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": 4, "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": 6, "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}]}') - - def test_dispatch_detail(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - resp = resource.dispatch_detail(request, pk=1) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - - def test_dispatch(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 4}, "objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": 4, "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": 6, "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}]}') - - resp = resource.dispatch('detail', request, pk=1) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - - # Check for an override. - request.META = { - 'HTTP_X_HTTP_METHOD_OVERRIDE': 'PATCH', - } - request._read_started = False - request._raw_post_data = request._body = '{"title": "Super-duper override ACTIVATE!"}' - resp = resource.dispatch('detail', request, pk=1) - self.assertEqual(resp.status_code, 202) - self.assertEqual(resp.content.decode('utf-8'), '') - self.assertEqual(Note.objects.get(pk=1).title, u'Super-duper override ACTIVATE!') - - def test_build_bundle(self): - resource = NoteResource() - - unpopulated_bundle = resource.build_bundle() - self.assertTrue(isinstance(unpopulated_bundle, Bundle)) - self.assertEqual(unpopulated_bundle.data, {}) - - populated_bundle = resource.build_bundle(data={'title': 'Foo'}) - self.assertTrue(isinstance(populated_bundle, Bundle)) - self.assertEqual(populated_bundle.data, {'title': 'Foo'}) - - req = HttpRequest() - req.GET = {'foo': 'bar'} - populated_bundle_with_request = resource.build_bundle(data={'title': 'Foo'}, request=req) - self.assertTrue(isinstance(populated_bundle_with_request, Bundle)) - self.assertEqual(populated_bundle_with_request.data, {'title': 'Foo'}) - self.assertEqual(populated_bundle_with_request.request.GET['foo'], 'bar') - - def test_obj_get_list(self): - resource = NoteResource() - base_bundle = Bundle() - - object_list = resource.obj_get_list(base_bundle) - self.assertEqual(len(object_list), 4) - self.assertEqual(object_list[0].title, u'First Post!') - - notes = NoteResource().obj_get_list(base_bundle) - self.assertEqual(len(notes), 4) - self.assertEqual(notes[0].is_active, True) - self.assertEqual(notes[0].title, u'First Post!') - self.assertEqual(notes[1].is_active, True) - self.assertEqual(notes[1].title, u'Another Post') - self.assertEqual(notes[2].is_active, True) - self.assertEqual(notes[2].title, u'Recent Volcanic Activity.') - self.assertEqual(notes[3].is_active, True) - self.assertEqual(notes[3].title, u"Granny's Gone") - - customs = VeryCustomNoteResource().obj_get_list(base_bundle) - self.assertEqual(len(customs), 6) - self.assertEqual(customs[0].is_active, True) - self.assertEqual(customs[0].title, u'First Post!') - self.assertEqual(customs[0].author.username, u'johndoe') - self.assertEqual(customs[1].is_active, True) - self.assertEqual(customs[1].title, u'Another Post') - self.assertEqual(customs[1].author.username, u'johndoe') - self.assertEqual(customs[2].is_active, False) - self.assertEqual(customs[2].title, u'Hello World!') - self.assertEqual(customs[2].author.username, u'janedoe') - self.assertEqual(customs[3].is_active, True) - self.assertEqual(customs[3].title, u'Recent Volcanic Activity.') - self.assertEqual(customs[3].author.username, u'janedoe') - self.assertEqual(customs[4].is_active, False) - self.assertEqual(customs[4].title, u'My favorite new show') - self.assertEqual(customs[4].author.username, u'johndoe') - self.assertEqual(customs[5].is_active, True) - self.assertEqual(customs[5].title, u"Granny's Gone") - self.assertEqual(customs[5].author.username, u'janedoe') - - # Ensure filtering by request params works. - mock_request = MockRequest() - mock_request.GET['title'] = u"Granny's Gone" - base_bundle.request = mock_request - notes = NoteResource().obj_get_list(bundle=base_bundle) - self.assertEqual(len(notes), 1) - self.assertEqual(notes[0].title, u"Granny's Gone") - - # Ensure kwargs override request params. - mock_request = MockRequest() - mock_request.GET['title'] = u"Granny's Gone" - base_bundle.request = mock_request - notes = NoteResource().obj_get_list(bundle=base_bundle, title='Recent Volcanic Activity.') - self.assertEqual(len(notes), 1) - self.assertEqual(notes[0].title, u'Recent Volcanic Activity.') - - def test_apply_filters(self): - nr = NoteResource() - mock_request = MockRequest() - - # No filters. - notes = nr.apply_filters(mock_request, {}) - self.assertEqual(len(notes), 4) - - filters = { - 'title': u"Granny's Gone" - } - notes = nr.apply_filters(mock_request, filters) - self.assertEqual(len(notes), 1) - self.assertEqual(notes[0].title, u"Granny's Gone") - - filters = { - 'title__icontains': u"post", - 'created__lte': datetime.date(2010, 6, 30), - } - notes = nr.apply_filters(mock_request, filters) - self.assertEqual(len(notes), 2) - self.assertEqual(notes[0].title, u'First Post!') - self.assertEqual(notes[1].title, u'Another Post') - - def test_obj_get(self): - resource = NoteResource() - base_bundle = Bundle() - - obj = resource.obj_get(base_bundle, pk=1) - self.assertTrue(isinstance(obj, Note)) - self.assertEqual(obj.title, u'First Post!') - - # Test non-pk gets. - obj = resource.obj_get(base_bundle, slug='another-post') - self.assertTrue(isinstance(obj, Note)) - self.assertEqual(obj.title, u'Another Post') - - note = NoteResource() - note_obj = note.obj_get(base_bundle, pk=1) - self.assertEqual(note_obj.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(note_obj.created, aware_datetime(2010, 3, 30, 20, 5)) - self.assertEqual(note_obj.is_active, True) - self.assertEqual(note_obj.slug, u'first-post') - self.assertEqual(note_obj.title, u'First Post!') - self.assertEqual(note_obj.updated, aware_datetime(2010, 3, 30, 20, 5)) - - custom = VeryCustomNoteResource() - custom_obj = custom.obj_get(base_bundle, pk=1) - self.assertEqual(custom_obj.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(custom_obj.created, aware_datetime(2010, 3, 30, 20, 5)) - self.assertEqual(custom_obj.is_active, True) - self.assertEqual(custom_obj.author.username, u'johndoe') - self.assertEqual(custom_obj.title, u'First Post!') - - related = RelatedNoteResource() - related_obj = related.obj_get(base_bundle, pk=1) - self.assertEqual(related_obj.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(related_obj.created, aware_datetime(2010, 3, 30, 20, 5)) - self.assertEqual(related_obj.is_active, True) - self.assertEqual(related_obj.author.username, u'johndoe') - self.assertEqual(related_obj.title, u'First Post!') - self.assertEqual(list(related_obj.subjects.values_list('id', flat=True)), [1, 2]) - - def test_obj_get_across_related(self): - new_user = User.objects.create(username='foo') - new_note = Note.objects.create(author=new_user) - - class RelatedFilterNoteResource(NoteResource): - author = fields.ToOneField(UserResource, 'author') - - class Meta(NoteResource.Meta): - filtering = { - 'author': ALL_WITH_RELATIONS, - } - - resource = RelatedFilterNoteResource() - base_bundle = Bundle() - - obj = resource.obj_get(base_bundle, author__username=new_user.username) - self.assertTrue(isinstance(obj, Note)) - self.assertEqual(obj, new_note) - - def test_uri_fields(self): - with_abs_url = WithAbsoluteURLNoteResource() - base_bundle = Bundle() - with_abs_url_obj = with_abs_url.obj_get(base_bundle, pk=1) - - with_abs_url_bundle = with_abs_url.build_bundle(obj=with_abs_url_obj) - abs_bundle = with_abs_url.full_dehydrate(with_abs_url_bundle) - self.assertEqual(abs_bundle.data['resource_uri'], '/api/v1/withabsoluteurlnote/1/') - self.assertEqual(abs_bundle.data['absolute_url'], u'/some/fake/path/1/') - - def test_jsonp_validation(self): - resource = NoteResource() - - # invalid JSONP callback should return Http400 - request = HttpRequest() - request.GET = {'format': 'jsonp', 'callback': '()'} - request.method = 'GET' - with self.assertRaises(BadRequest): - resp = resource.dispatch_detail(request, pk=1) - - # Try again with ``wrap_view`` for sanity. - resp = resource.wrap_view('dispatch_detail')(request, pk=1) - self.assertEqual(resp.status_code, 400) - self.assertEqual(force_text(resp.content), '{"error": "JSONP callback name is invalid."}') - self.assertEqual(resp['content-type'], 'application/json') - - # valid JSONP callback should work - request = HttpRequest() - request.GET = {'format': 'jsonp', 'callback': 'myCallback'} - request.method = 'GET' - resp = resource.dispatch_detail(request, pk=1) - self.assertEqual(resp.status_code, 200) - - def test_get_schema(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Patch the ``created/updated`` defaults for testability. - with patch.object(resource.fields['created'], '_default', new=aware_datetime(2011, 9, 24, 0, 2)),\ - patch.object(resource.fields['updated'], '_default', new=aware_datetime(2011, 9, 24, 0, 2)): - resp = resource.get_schema(request) - - self.assertEqual(resp.status_code, 200) - - expected_schema = { - "allowed_detail_http_methods": ["get", "post", "put", "delete", "patch"], - "allowed_list_http_methods": ["get", "post", "put", "delete", "patch"], - "default_format": "application/json", - "default_limit": 20, - "fields": { - "content": { - "blank": True, - "default": "", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'content', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "created": { - "blank": False, - "default": "2011-09-24T00:02:00", - "help_text": "A date & time as a string. Ex: \"2010-11-10T03:07:43\"", - "verbose_name": 'created', - "nullable": False, - "readonly": False, - "type": "datetime", - "unique": False, - "primary_key": False - }, - "id": { - "blank": True, - "default": "", - "help_text": "Integer data. Ex: 2673", - "verbose_name": 'ID', - "nullable": False, - "readonly": False, - "type": "integer", - "unique": True, - "primary_key": True - }, - "is_active": { - "blank": True, - "default": True, - "help_text": "Boolean data. Ex: True", - "verbose_name": 'is active', - "nullable": False, - "readonly": False, - "type": "boolean", - "unique": False, - "primary_key": False - }, - "resource_uri": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'resource uri', - "nullable": False, - "readonly": True, - "type": "string", - "unique": False, - "primary_key": False - }, - "slug": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'slug', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "title": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'The Title', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "updated": { - "blank": False, - "default": "2011-09-24T00:02:00", - "help_text": "A date & time as a string. Ex: \"2010-11-10T03:07:43\"", - "verbose_name": 'updated', - "nullable": False, - "readonly": False, - "type": "datetime", - "unique": False, - "primary_key": False - } - }, - "filtering": { - "content": ["startswith", "exact"], - "slug": ["exact"], - "created": 1, - "title": 1 - }, - "ordering": ["title", "slug", "resource_uri"], - } - - schema = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(schema, expected_schema) - - def test_get_schema_with_related(self): - resource = RelatedNoteResource() - - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Patch the ``created/updated`` defaults for testability. - with patch.object(resource.fields['created'], '_default', new=aware_datetime(2011, 9, 24, 0, 2)): - resp = resource.get_schema(request) - - self.assertEqual(resp.status_code, 200) - - expected_schema = { - "allowed_detail_http_methods": ["get", "post", "put", "delete", "patch"], - "allowed_list_http_methods": ["get", "post", "put", "delete", "patch"], - "default_format": "application/json", - "default_limit": 20, - "fields": { - "content": { - "blank": True, - "default": "", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'content', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "created": { - "blank": False, - "default": "2011-09-24T00:02:00", - "help_text": "A date & time as a string. Ex: \"2010-11-10T03:07:43\"", - "verbose_name": 'created', - "nullable": False, - "readonly": False, - "type": "datetime", - "unique": False, - "primary_key": False - }, - "is_active": { - "blank": True, - "default": True, - "help_text": "Boolean data. Ex: True", - "verbose_name": 'is active', - "nullable": False, - "readonly": False, - "type": "boolean", - "unique": False, - "primary_key": False - }, - "resource_uri": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'resource uri', - "nullable": False, - "readonly": True, - "type": "string", - "unique": False, - "primary_key": False - }, - "slug": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'slug', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "title": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'The Title', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - 'author': { - "blank": False, - "default": "No default provided.", - "help_text": 'A single related resource. Can be either a URI or set of nested resource data.', - "verbose_name": 'The Author', - "readonly": False, - "nullable": False, - "type": 'related', - "unique": False, - "primary_key": False, - "related_schema": '/api/v1/user/schema/', - "related_type": 'to_one' - }, - 'subjects': { - "blank": False, - "default": "No default provided.", - "help_text": 'Many related resources. Can be either a list of URIs or list of individually nested resource data.', - "verbose_name": 'subjects', - "readonly": False, - "nullable": False, - "type": 'related', - "unique": False, - "primary_key": False, - "related_schema": '/api/v1/subjects/schema/', - "related_type": 'to_many' - } - }, - 'default_format': 'application/json', - 'filtering': { - 'author': ALL, - 'subjects': ALL_WITH_RELATIONS, - }, - } - - schema = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(schema, expected_schema) - - def test_get_schema_with_related_resource_not_in_urls(self): - """ - Test case for #1439. Need to handle schemas for related resources that - aren't in any urlconfs. - """ - class GhostResource(Resource): - foo = fields.CharField() - - class Meta: - object_class = object - - class CustomRelatedNoteResource(RelatedNoteResource): - ghost = fields.ToOneField(GhostResource, 'ghost') - - Meta = RelatedNoteResource.Meta - - resource = CustomRelatedNoteResource() - - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Patch the ``created/updated`` defaults for testability. - with patch.object(resource.fields['created'], '_default', new=aware_datetime(2011, 9, 24, 0, 2)): - resp = resource.get_schema(request) - - self.assertEqual(resp.status_code, 200) - - expected_schema = { - "allowed_detail_http_methods": ["get", "post", "put", "delete", "patch"], - "allowed_list_http_methods": ["get", "post", "put", "delete", "patch"], - "default_format": "application/json", - "default_limit": 20, - "fields": { - "content": { - "blank": True, - "default": "", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'content', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "created": { - "blank": False, - "default": "2011-09-24T00:02:00", - "help_text": "A date & time as a string. Ex: \"2010-11-10T03:07:43\"", - "verbose_name": 'created', - "nullable": False, - "readonly": False, - "type": "datetime", - "unique": False, - "primary_key": False - }, - "is_active": { - "blank": True, - "default": True, - "help_text": "Boolean data. Ex: True", - "verbose_name": 'is active', - "nullable": False, - "readonly": False, - "type": "boolean", - "unique": False, - "primary_key": False - }, - "resource_uri": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'resource uri', - "nullable": False, - "readonly": True, - "type": "string", - "unique": False, - "primary_key": False - }, - "slug": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'slug', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - "title": { - "blank": False, - "default": "No default provided.", - "help_text": "Unicode string data. Ex: \"Hello World\"", - "verbose_name": 'The Title', - "nullable": False, - "readonly": False, - "type": "string", - "unique": False, - "primary_key": False - }, - 'ghost': { - "blank": False, - "default": "No default provided.", - "help_text": 'A single related resource. Can be either a URI or set of nested resource data.', - "verbose_name": 'ghost', - "readonly": False, - "nullable": False, - "type": 'related', - "unique": False, - "primary_key": False, - "related_schema": '', - "related_type": 'to_one' - } - }, - 'default_format': 'application/json', - 'filtering': { - 'author': ALL, - 'subjects': ALL_WITH_RELATIONS, - }, - } - - schema = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(schema['fields'], expected_schema['fields']) - - def test_get_multiple(self): - resource = NoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - resp = resource.get_multiple(request, pk_list='1') - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}]}') - - resp = resource.get_multiple(request, pk_list='1;2') - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}]}') - - resp = resource.get_multiple(request, pk_list='2;3') - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"not_found": ["3"], "objects": [{"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}]}') - - resp = resource.get_multiple(request, pk_list='1;2;4;6') - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": 4, "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": 6, "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}]}') - - def test_get_multiple_use_in(self): - resource = AlwaysDataNoteResourceUseIn() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - resp = resource.get_multiple(request, pk_list='1') - self.assertEqual(resp.status_code, 200) - content = json.loads(resp.content.decode('utf-8')) - for note in content['objects']: - self.assertIn('constant', note) - self.assertNotIn('author', note) - - resp = resource.get_multiple(request, pk_list='1;2') - self.assertEqual(resp.status_code, 200) - content = json.loads(resp.content.decode('utf-8')) - for note in content['objects']: - self.assertIn('constant', note) - self.assertNotIn('author', note) - - resp = resource.get_multiple(request, pk_list='2;3') - self.assertEqual(resp.status_code, 200) - content = json.loads(resp.content.decode('utf-8')) - for note in content['objects']: - self.assertIn('constant', note) - self.assertNotIn('author', note) - - resp = resource.get_multiple(request, pk_list='1;2;4;6') - self.assertEqual(resp.status_code, 200) - content = json.loads(resp.content.decode('utf-8')) - for note in content['objects']: - self.assertIn('constant', note) - self.assertNotIn('author', note) - - @patch('tastypie.throttle.time') - @override_settings(DEBUG=False) - def test_check_bool_throttling(self, mocked_time): - mocked_time.time.return_value = time.time() - - resource = ThrottledNoteResource() - _orginal_throttle = resource._meta.throttle - - class BoolThrottle(resource._meta.throttle.__class__): - def should_be_throttled(self, *args, **kwargs): - ret = super(BoolThrottle, self).should_be_throttled(*args, **kwargs) - if ret: - return True - return False - resource._meta.throttle = BoolThrottle( - throttle_at=resource._meta.throttle.throttle_at, - timeframe=resource._meta.throttle.timeframe, - expiration=resource._meta.throttle.expiration - ) - - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Not throttled. - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 1) - - # Not throttled. - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Throttled. - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch('list', request) - e = ctx.exception - self.assertEqual(e.response.status_code, 429) - self.assertNotIn('Retry-After', e.response) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Throttled. - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch('list', request) - e = ctx.exception - self.assertEqual(e.response.status_code, 429) - self.assertNotIn('Retry-After', e.response) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Check the ``wrap_view``. - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp.status_code, 429) - self.assertNotIn('Retry-After', resp) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - resource._meta.throttle = _orginal_throttle - - @patch('tastypie.throttle.time') - @override_settings(DEBUG=False) - def test_check_int_throttling(self, mocked_time): - mocked_time.time.return_value = time.time() - - resource = ThrottledNoteResource() - _orginal_throttle = resource._meta.throttle - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Not throttled. - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 1) - - # Not throttled. - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Throttled. - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch('list', request) - e = ctx.exception - self.assertEqual(e.response.status_code, 429) - self.assertEqual(e.response['Retry-After'], '5') - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Throttled. - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch('list', request) - e = ctx.exception - self.assertEqual(e.response.status_code, 429) - self.assertEqual(e.response['Retry-After'], '5') - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Check the ``wrap_view``. - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp.status_code, 429) - self.assertEqual(resp['Retry-After'], '5') - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - resource._meta.throttle = _orginal_throttle - - @patch('tastypie.throttle.time') - @override_settings(DEBUG=False) - def test_check_datetime_throttling(self, mocked_time): - mocked_time.time.return_value = time.time() - - retry_after = datetime.datetime(year=2014, month=8, day=8, hour=8, minute=55, tzinfo=timezone.utc) - retry_after_str = 'Fri, 08 Aug 2014 14:55:00 GMT' - - resource = ThrottledNoteResource() - _orginal_throttle = resource._meta.throttle - - class DatetimeThrottle(resource._meta.throttle.__class__): - def should_be_throttled(self, *args, **kwargs): - ret = super(DatetimeThrottle, self).should_be_throttled(*args, **kwargs) - if ret: - return retry_after - return False - resource._meta.throttle = DatetimeThrottle( - throttle_at=resource._meta.throttle.throttle_at, - timeframe=resource._meta.throttle.timeframe, - expiration=resource._meta.throttle.expiration - ) - - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Not throttled. - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 1) - - # Not throttled. - resp = resource.dispatch('list', request) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Throttled. - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch('list', request) - e = ctx.exception - self.assertEqual(e.response.status_code, 429) - self.assertEqual(e.response['Retry-After'], retry_after_str) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Throttled. - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch('list', request) - e = ctx.exception - self.assertEqual(e.response.status_code, 429) - self.assertEqual(e.response['Retry-After'], retry_after_str) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - # Check the ``wrap_view``. - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp.status_code, 429) - self.assertEqual(resp['Retry-After'], retry_after_str) - self.assertEqual(len(cache.get('noaddr_nohost_accesses')), 2) - - resource._meta.throttle = _orginal_throttle - - def test_generate_cache_key(self): - resource = NoteResource(api_name='v1') - self.assertEqual(resource.generate_cache_key(), 'v1:notes::') - self.assertEqual(resource.generate_cache_key('abc', '123'), 'v1:notes:abc:123:') - self.assertEqual(resource.generate_cache_key(foo='bar', moof='baz'), 'v1:notes::foo=bar:moof=baz') - self.assertEqual(resource.generate_cache_key('abc', '123', foo='bar', moof='baz'), 'v1:notes:abc:123:foo=bar:moof=baz') - - def test_cached_fetch_list(self): - resource = NoteResource() - base_bundle = Bundle() - - object_list = resource.cached_obj_get_list(base_bundle) - self.assertEqual(len(object_list), 4) - self.assertEqual(object_list[0].title, u'First Post!') - - def test_cached_fetch_detail(self): - resource = NoteResource() - base_bundle = Bundle() - - obj = resource.cached_obj_get(base_bundle, pk=1) - self.assertTrue(isinstance(obj, Note)) - self.assertEqual(obj.title, u'First Post!') - - def test_configuration(self): - note = NoteResource() - self.assertEqual(len(note.fields), 8) - self.assertEqual(sorted(note.fields.keys()), ['content', 'created', 'id', 'is_active', 'resource_uri', 'slug', 'title', 'updated']) - self.assertEqual(note.fields['content'].default, '') - - custom = VeryCustomNoteResource() - self.assertEqual(len(custom.fields), 7) - self.assertEqual(sorted(custom.fields.keys()), ['author', 'constant', 'content', 'created', 'is_active', 'resource_uri', 'title']) - - no_uri = NoUriNoteResource() - self.assertEqual(len(no_uri.fields), 7) - self.assertEqual(sorted(no_uri.fields.keys()), ['content', 'created', 'id', 'is_active', 'slug', 'title', 'updated']) - - with_abs_url = WithAbsoluteURLNoteResource() - self.assertEqual(len(with_abs_url.fields), 9) - self.assertEqual(sorted(with_abs_url.fields.keys()), ['absolute_url', 'content', 'created', 'id', 'is_active', 'resource_uri', 'slug', 'title', 'updated']) - - def test_obj_delete_list_custom_qs(self): - self.assertEqual(len(Note.objects.all()), 6) - base_bundle = Bundle() - NoteResource().obj_delete_list(base_bundle) - self.assertEqual(len(Note.objects.all()), 2) - - def test_obj_delete_list_basic_qs(self): - self.assertEqual(len(Note.objects.all()), 6) - base_bundle = Bundle() - VeryCustomNoteResource().obj_delete_list(base_bundle) - self.assertEqual(len(Note.objects.all()), 0) - - def test_obj_delete_list_non_queryset(self): - class NonQuerysetNoteResource(ModelResource): - class Meta: - queryset = Note.objects.all() - - def authorized_delete_list(self, object_list, bundle): - return tuple(object_list[:2]) - - request = HttpRequest() - request.method = 'DELETE' - self.assertEqual(len(Note.objects.all()), 6) - # This is a regression. Used to fail miserably. - NonQuerysetNoteResource().delete_list(request=request) - self.assertEqual(len(Note.objects.all()), 4) - - def test_obj_delete_list_filtered(self): - self.assertEqual(Note.objects.all().count(), 6) - - note_to_delete = Note.objects.filter(is_active=True)[0] - - request = HttpRequest() - request.method = 'DELETE' - request.GET = {'slug': str(note_to_delete.slug)} - NoteResource().delete_list(request=request) - self.assertEqual(len(Note.objects.all()), 5) - - def test_obj_create(self): - self.assertEqual(Note.objects.all().count(), 6) - note = NoteResource() - bundle = Bundle(data={ - 'title': "A new post!", - 'slug': "a-new-post", - 'content': "Testing, 1, 2, 3!", - 'is_active': True - }) - note.obj_create(bundle) - self.assertEqual(Note.objects.all().count(), 7) - latest = Note.objects.get(slug='a-new-post') - self.assertEqual(latest.title, u"A new post!") - self.assertEqual(latest.slug, u'a-new-post') - self.assertEqual(latest.content, u'Testing, 1, 2, 3!') - self.assertEqual(latest.is_active, True) - - self.assertEqual(Note.objects.all().count(), 7) - note = RelatedNoteResource() - related_bundle = Bundle(data={ - 'title': "Yet another new post!", - 'slug': "yet-another-new-post", - 'content': "WHEEEEEE!", - 'is_active': True, - 'author': '/api/v1/user/1/', - 'subjects': ['/api/v1/subjects/2/'], - }) - note.obj_create(related_bundle) - self.assertEqual(Note.objects.all().count(), 8) - latest = Note.objects.get(slug='yet-another-new-post') - self.assertEqual(latest.title, u"Yet another new post!") - self.assertEqual(latest.slug, u'yet-another-new-post') - self.assertEqual(latest.content, u'WHEEEEEE!') - self.assertEqual(latest.is_active, True) - self.assertEqual(latest.author.username, u'johndoe') - self.assertEqual(latest.subjects.all().count(), 1) - self.assertEqual([sub.id for sub in latest.subjects.all()], [2]) - - self.assertEqual(Note.objects.all().count(), 8) - note = AnotherRelatedNoteResource() - related_bundle = Bundle(data={ - 'title': "Yet another another new post!", - 'slug': "yet-another-another-new-post", - 'content': "WHEEEEEE!", - 'is_active': True, - 'author': '/api/v1/user/1/', - 'subjects': [{ - 'name': 'helloworld', - 'url': 'http://example.com', - 'created': '2010-05-20 14:22:00', - }], - }) - note.obj_create(related_bundle) - self.assertEqual(Note.objects.all().count(), 9) - latest = Note.objects.get(slug='yet-another-another-new-post') - self.assertEqual(latest.title, u"Yet another another new post!") - self.assertEqual(latest.slug, u'yet-another-another-new-post') - self.assertEqual(latest.content, u'WHEEEEEE!') - self.assertEqual(latest.is_active, True) - self.assertEqual(latest.author.username, u'johndoe') - self.assertEqual(latest.subjects.all().count(), 1) - self.assertEqual([sub.id for sub in latest.subjects.all()], [3]) - - self.assertEqual(Note.objects.all().count(), 9) - self.assertEqual(User.objects.filter(username='snerble').count(), 0) - note = YetAnotherRelatedNoteResource() - related_bundle = Bundle(data={ - 'title': "Yet yet another another new post!", - 'slug': "yet-yet-another-another-new-post", - 'content': "WHOA!!!", - 'is_active': True, - 'author': { - 'username': 'snerble', - 'password': 'hunter42', - }, - 'subjects': [], - }) - note.obj_create(related_bundle) - self.assertEqual(Note.objects.all().count(), 10) - latest = Note.objects.get(slug='yet-yet-another-another-new-post') - self.assertEqual(latest.title, u"Yet yet another another new post!") - self.assertEqual(latest.slug, u'yet-yet-another-another-new-post') - self.assertEqual(latest.content, u'WHOA!!!') - self.assertEqual(latest.is_active, True) - self.assertEqual(latest.author.username, u'snerble') - self.assertEqual(latest.subjects.all().count(), 0) - - note = RequiredFKNoteResource() - related_bundle = Bundle(data={ - 'slug': 'note-with-editor', - 'editor': { - 'username': 'zeus', - 'password': 'apollo', - }, - }) - note.obj_create(related_bundle) - latest = NoteWithEditor.objects.get(slug='note-with-editor') - self.assertEqual(latest.editor.username, u'zeus') - - def test_obj_create_full_hydrate_on_create_authorization(self): - cr = CounterCreateDetailResource() - counter_bundle = cr.build_bundle(data={ - "name": "About", - "slug": "about", - }, obj=Counter()) - cr.obj_create(counter_bundle) - - self.assertEquals(counter_bundle._create_auth_call_count, 1) - self.assertEquals(counter_bundle.obj.name, "About") - self.assertEquals(counter_bundle.obj.slug, "about") - - def test_obj_update(self): - self.assertEqual(Note.objects.all().count(), 6) - - note = NoteResource() - base_bundle = Bundle() - note_obj = note.obj_get(base_bundle, pk=1) - note_bundle = note.build_bundle(obj=note_obj) - note_bundle = note.full_dehydrate(note_bundle) - note_bundle.data['title'] = 'Whee!' - with self.assertNumQueries(1): - note.obj_update(note_bundle, pk=1) - self.assertEqual(Note.objects.all().count(), 6) - numero_uno = Note.objects.get(pk=1) - self.assertEqual(numero_uno.title, u'Whee!') - self.assertEqual(numero_uno.slug, u'first-post') - self.assertEqual(numero_uno.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(numero_uno.is_active, True) - - # same setup as above, just need to test with '1' as the pk (str - # instead of int) - note = NoteResource() - base_bundle = Bundle() - note_obj = note.obj_get(base_bundle, pk=1) - note_bundle = note.build_bundle(obj=note_obj) - note_bundle = note.full_dehydrate(note_bundle) - note_bundle.data['title'] = 'Whee!' - with self.assertNumQueries(1): - note.obj_update(note_bundle, pk='1') - self.assertEqual(Note.objects.all().count(), 6) - - self.assertEqual(Note.objects.all().count(), 6) - note = RelatedNoteResource() - related_obj = note.obj_get(base_bundle, pk=1) - related_bundle = Bundle(obj=related_obj, data={ - 'title': "Yet another new post!", - 'slug': "yet-another-new-post", - 'content': "WHEEEEEE!", - 'is_active': True, - 'author': '/api/v1/user/2/', - 'subjects': ['/api/v1/subjects/2/', '/api/v1/subjects/1/'], - }) - note.obj_update(related_bundle, pk=1) - self.assertEqual(Note.objects.all().count(), 6) - latest = Note.objects.get(slug='yet-another-new-post') - self.assertEqual(latest.title, u"Yet another new post!") - self.assertEqual(latest.slug, u'yet-another-new-post') - self.assertEqual(latest.content, u'WHEEEEEE!') - self.assertEqual(latest.is_active, True) - self.assertEqual(latest.author.username, u'janedoe') - self.assertEqual(latest.subjects.all().count(), 2) - self.assertEqual([sub.id for sub in latest.subjects.all()], [1, 2]) - - self.assertEqual(Note.objects.all().count(), 6) - note = AnotherRelatedNoteResource() - related_obj = note.obj_get(base_bundle, pk=1) - related_bundle = Bundle(data={ - 'title': "Yet another another new post!", - 'slug': "yet-another-another-new-post", - 'content': "WHEEEEEE!", - 'is_active': True, - 'author': '/api/v1/user/1/', - 'subjects': [{ - 'name': 'helloworld', - 'url': 'http://example.com', - 'created': '2010-05-20 14:22:00', - }], - }) - note.obj_update(related_bundle, pk=1) - self.assertEqual(Note.objects.all().count(), 6) - latest = Note.objects.get(slug='yet-another-another-new-post') - self.assertEqual(latest.title, u"Yet another another new post!") - self.assertEqual(latest.slug, u'yet-another-another-new-post') - self.assertEqual(latest.content, u'WHEEEEEE!') - self.assertEqual(latest.is_active, True) - self.assertEqual(latest.author.username, u'johndoe') - self.assertEqual(latest.subjects.all().count(), 1) - self.assertEqual([sub.id for sub in latest.subjects.all()], [3]) - - # Fix non-native types (like datetimes) during attempted hydration. - # This ensures that handing the wrong type should get coerced to the - # right thing. - self.assertEqual(Note.objects.all().count(), 6) - note = NoteResource() - note_obj = note.obj_get(base_bundle, pk=1) - self.assertEqual(note_obj.title, u'Yet another another new post!') - self.assertEqual(note_obj.created, aware_datetime(2010, 3, 30, 20, 5)) - note_bundle = note.build_bundle(obj=note_obj) - note_bundle = note.full_dehydrate(note_bundle) - note_bundle.data['title'] = 'OMGOMGOMGOMG!' - note_bundle.data['created'] = aware_datetime(2011, 11, 23, 1, 0, 0) - note.obj_update(note_bundle, pk=1, created='2010-03-30T20:05:00') - self.assertEqual(Note.objects.all().count(), 6) - numero_uno = Note.objects.get(pk=1) - self.assertEqual(numero_uno.title, u'OMGOMGOMGOMG!') - self.assertEqual(numero_uno.slug, u'yet-another-another-new-post') - self.assertEqual(numero_uno.content, u'WHEEEEEE!') - self.assertEqual(numero_uno.created, aware_datetime(2011, 11, 23, 1, 0)) - - # Now try a lookup that should fail. - note = NoteResource() - note_bundle = note.build_bundle(data={ - "author": "/api/v1/user/1/", - "title": "Something something Post!", - "slug": "something-something-post", - "content": "Stock post content.", - "is_active": True, - "created": "2011-03-30 20:05:00", - "updated": "2011-03-30 20:05:00" - }) - self.assertRaises(NotFound, note.obj_update, note_bundle, pk=1, created='2010-03-31T20:05:00') - self.assertEqual(Note.objects.all().count(), 6) - - # Assign based on the ``request.user``, which helps ensure that - # the correct ``request`` is being passed along. - request = HttpRequest() - request.user = User.objects.get(username='johndoe') - base_bundle.request = request - self.assertEqual(AlwaysUserNoteResource().get_object_list(request).count(), 2) - note = AlwaysUserNoteResource() - note_obj = note.obj_get(base_bundle, pk=1) - note_bundle = note.build_bundle(obj=note_obj) - note_bundle = note.full_dehydrate(note_bundle) - note_bundle.data['title'] = 'Whee!' - note_bundle.request = request - note.obj_update(note_bundle, pk=1) - self.assertEqual(Note.objects.all().count(), 6) - numero_uno = Note.objects.get(pk=1) - self.assertEqual(numero_uno.title, u'Whee!') - self.assertEqual(numero_uno.slug, u'yet-another-another-new-post') - self.assertEqual(numero_uno.content, u'WHEEEEEE!') - self.assertEqual(numero_uno.is_active, True) - self.assertEqual(numero_uno.author.pk, request.user.pk) - - def test_obj_update_single_hydrate(self): - counter = Counter.objects.get(pk=1) - - self.assertEqual(counter.count, 1) - - cr = CounterResource() - counter_bundle = cr.build_bundle(data={ - "pk": counter.pk, - "name": "Signups", - "slug": "signups", - }) - cr.obj_update(counter_bundle, pk=1) - - self.assertEqual(Counter.objects.all().count(), 2) - counter = Counter.objects.get(pk=1) - self.assertEqual(counter.count, 1) - - def test_obj_update_full_hydrate_on_update_authorization(self): - counter = Counter.objects.get(pk=1) - - cr = CounterUpdateDetailResource() - counter_bundle = cr.build_bundle(data={ - "pk": counter.pk, - "name": "Signups", - "slug": "signups", - }, obj=Counter()) - cr.obj_update(counter_bundle, pk=1) - - counter = Counter.objects.get(pk=1) - self.assertEquals(counter_bundle._update_auth_call_count, 1) - self.assertEquals(counter_bundle.obj.name, "Signups") - self.assertEquals(counter_bundle.obj.slug, "signups") - - def test_lookup_kwargs_with_identifiers__field_without_attr(self): - """ - Verify PR #942 fixed. - """ - class FieldWithoutAttributeNoteResource(NoteResource): - noattrfield = fields.CharField() - note = FieldWithoutAttributeNoteResource() - note_bundle = note.build_bundle() - note_bundle.data['title'] = 'Whee!' - - # need to use ordered dict to make sure 'noattrfield' comes before - # 'id', otherwise the value of 'id' gets used for 'noattrfield'. - kwargs = OrderedDict([('noattrfield', 'foo'), ('id', 1)]) - lookup_kwargs = note.lookup_kwargs_with_identifiers(note_bundle, kwargs) - - self.assertEqual(lookup_kwargs, {'id': 1}) - - def test_obj_delete(self): - self.assertEqual(Note.objects.all().count(), 6) - note = NoteResource() - base_bundle = Bundle() - note.obj_delete(base_bundle, pk=1) - self.assertEqual(Note.objects.all().count(), 5) - self.assertRaises(Note.DoesNotExist, Note.objects.get, pk=1) - - # Test non-pk deletes. - base_bundle = Bundle() - note.obj_delete(base_bundle, slug='another-post') - self.assertEqual(Note.objects.all().count(), 4) - self.assertRaises(Note.DoesNotExist, Note.objects.get, slug='another-post') - - def test_rollback(self): - self.assertEqual(Note.objects.all().count(), 6) - note = NoteResource() - - bundles_seen = [] - note.rollback(bundles_seen) - self.assertEqual(Note.objects.all().count(), 6) - - # The one that exists should be deleted, the others ignored. - bundles_seen = [Bundle(obj=Note.objects.get(pk=1)), Bundle(obj=Note()), Bundle()] - note.rollback(bundles_seen) - self.assertEqual(Note.objects.all().count(), 5) - - def test_is_valid(self): - # Using the plug. - note = NoteResource() - bundle = Bundle(data={}) - - try: - note.is_valid(bundle) - except: - self.fail("Stock 'is_valid' should pass without exception.") - - # An actual form. - class NoteForm(forms.Form): - title = forms.CharField(max_length=100) - slug = forms.CharField(max_length=50) - content = forms.CharField(required=False, widget=forms.Textarea) - is_active = forms.BooleanField() - - # Define a custom clean to make sure non-field errors are making it - # through. - def clean(self): - if not self.cleaned_data.get('content', ''): - raise forms.ValidationError('Having no content makes for a very boring note.') - - return self.cleaned_data - - class ValidatedNoteResource(ModelResource): - class Meta: - queryset = Note.objects.all() - resource_name = 'validated' - validation = FormValidation(form_class=NoteForm) - - class ValidatedXMLNoteResource(ModelResource): - class Meta: - queryset = Note.objects.all() - resource_name = 'validated' - validation = FormValidation(form_class=NoteForm) - default_format = 'application/xml' - - validated = ValidatedNoteResource() - - # Test empty data. - bundle = Bundle(data={}) - self.assertFalse(validated.is_valid(bundle)) - self.assertEqual(bundle.errors, {'validated': {'is_active': [u'This field is required.'], 'slug': [u'This field is required.'], '__all__': [u'Having no content makes for a very boring note.'], 'title': [u'This field is required.']}}) - - # Test something that fails validation. - bundle = Bundle(data={ - 'title': 123, - 'slug': '123456789012345678901234567890123456789012345678901234567890', - 'content': '', - 'is_active': True, - }) - self.assertFalse(validated.is_valid(bundle)) - self.assertEqual(bundle.errors, {'validated': {'slug': [u'Ensure this value has at most 50 characters (it has 60).'], '__all__': [u'Having no content makes for a very boring note.']}}) - - # Test something that passes validation. - bundle = Bundle(data={ - 'title': 'Test Content', - 'slug': 'test-content', - 'content': "It doesn't get any more awesome than this.", - 'is_active': True, - }) - - self.assertTrue(validated.is_valid(bundle)) - - def test_self_referential(self): - class SelfResource(ModelResource): - me_baby_me = fields.ToOneField('self', 'parent', null=True) - - class Meta: - queryset = Note.objects.all() - resource_name = 'me_baby_me' - - me_baby_me = SelfResource() - self.assertEqual(len(me_baby_me.fields), 9) - self.assertEqual(me_baby_me._meta.resource_name, 'me_baby_me') - self.assertEqual(me_baby_me.fields['me_baby_me'].to, 'self') - self.assertEqual(me_baby_me.fields['me_baby_me'].to_class, SelfResource) - - class AnotherSelfResource(SelfResource): - class Meta: - queryset = Note.objects.all() - resource_name = 'another_me_baby_me' - - another_me_baby_me = AnotherSelfResource() - self.assertEqual(len(another_me_baby_me.fields), 9) - self.assertEqual(another_me_baby_me._meta.resource_name, 'another_me_baby_me') - self.assertEqual(another_me_baby_me.fields['me_baby_me'].to, 'self') - self.assertEqual(another_me_baby_me.fields['me_baby_me'].to_class, AnotherSelfResource) - - # make sure these remained the same - self.assertEqual(len(me_baby_me.fields), 9) - self.assertEqual(me_baby_me._meta.resource_name, 'me_baby_me') - self.assertEqual(me_baby_me.fields['me_baby_me'].to, 'self') - self.assertEqual(me_baby_me.fields['me_baby_me'].to_class, SelfResource) - - def test_subclassing(self): - class MiniResource(ModelResource): - abcd = fields.CharField(default='abcd') - efgh = fields.IntegerField(default=1234) - - class Meta: - queryset = Note.objects.all() - resource_name = 'mini' - - mini = MiniResource() - self.assertEqual(len(mini.fields), 10) - self.assertEqual(len(mini._meta.queryset.all()), 6) - self.assertEqual(mini._meta.resource_name, 'mini') - - class AnotherMiniResource(MiniResource): - ijkl = fields.BooleanField(default=True) - - class Meta: - queryset = Note.objects.all() - resource_name = 'anothermini' - - another = AnotherMiniResource() - self.assertEqual(len(another.fields), 11) - self.assertEqual(len(another._meta.queryset.all()), 6) - self.assertEqual(another._meta.resource_name, 'anothermini') - - class YetAnotherMiniResource(MiniResource): - mnop = fields.FloatField(default=True) - - class Meta: - queryset = Note.objects.all() - resource_name = 'yetanothermini' - fields = ['title', 'abcd', 'mnop'] - include_absolute_url = True - - yetanother = YetAnotherMiniResource() - self.assertEqual(len(yetanother.fields), 5) - self.assertEqual(sorted(yetanother.fields.keys()), ['abcd', 'absolute_url', 'mnop', 'resource_uri', 'title']) - self.assertEqual(len(yetanother._meta.queryset.all()), 6) - self.assertEqual(yetanother._meta.resource_name, 'yetanothermini') - - def test_nullable_toone_full_hydrate(self): - nrrnr = NullableRelatedNoteResource() - - # Regression: not specifying the ToOneField should still work if - # it is nullable. - bundle_1 = Bundle(data={ - 'subjects': [], - }) - - hydrated1 = nrrnr.full_hydrate(bundle_1) - - self.assertEqual(hydrated1.data.get('author'), None) - self.assertEqual(hydrated1.data['subjects'], []) - - def test_optional_required_data(self): - # Regression: You have a FK field that's required on the model - # but you want to optionally allow the user to omit it and use - # custom ``hydrate_*`` method to populate it if it's not - # present. - nmbr = NullableMediaBitResource() - - bundle_1 = Bundle(data={ - 'title': "Foo", - }) - - with self.assertRaises(ValueError): - # This is where things blow up, because you can't assign - # ``None`` to a required FK. - hydrated1 = nmbr.full_hydrate(bundle_1) - - # So we introduced ``blank=True``. - bmbr = BlankMediaBitResource() - hydrated1 = bmbr.full_hydrate(bundle_1) - self.assertEqual(hydrated1.obj.title, "Foo") - self.assertEqual(hydrated1.obj.note.pk, 1) - - def test_nullable_tomany_full_hydrate(self): - nrrnr = NullableRelatedNoteResource() - bundle_1 = Bundle(data={ - 'author': '/api/v1/user/1/', - 'subjects': [], - }) - - # Now load up the data. - hydrated = nrrnr.full_hydrate(bundle_1) - hydrated = nrrnr.hydrate_m2m(hydrated) - - self.assertEqual(hydrated.data['author'], '/api/v1/user/1/') - self.assertEqual(hydrated.data['subjects'], []) - - # Regression: not specifying the tomany field should still work if - # it is nullable. - bundle_2 = Bundle(data={ - 'author': '/api/v1/user/1/', - }) - - hydrated2 = nrrnr.full_hydrate(bundle_2) - hydrated2 = nrrnr.hydrate_m2m(hydrated2) - - self.assertEqual(hydrated2.data['author'], '/api/v1/user/1/') - self.assertEqual(hydrated2.data['subjects'], []) - - # Regression pt. II - Make sure saving the objects works. - nrrnr.obj_create(bundle_2) - self.assertEqual(hydrated2.obj.author.username, u'johndoe') - self.assertEqual(hydrated2.obj.subjects.count(), 0) - - def test_per_user_authorization(self): - from django.contrib.auth.models import AnonymousUser, User - - punr = PerUserNoteResource() - empty_request = HttpRequest() - empty_request.method = 'GET' - empty_request.GET = {'format': 'json'} - - anony_request = HttpRequest() - anony_request.method = 'GET' - anony_request.GET = {'format': 'json'} - anony_request.user = AnonymousUser() - - authed_request = HttpRequest() - authed_request.method = 'GET' - authed_request.GET = {'format': 'json'} - authed_request.user = User.objects.get(username='johndoe') - - authed_request_2 = HttpRequest() - authed_request_2.method = 'GET' - authed_request_2.GET = {'format': 'json'} - authed_request_2.user = User.objects.get(username='janedoe') - - self.assertEqual(punr._meta.queryset.count(), 6) - - # Requests without a user get all active objects, regardless of author. - empty_bundle = punr.build_bundle(request=empty_request) - self.assertEqual(punr.authorized_read_list(punr.get_object_list(empty_request), empty_bundle).count(), 4) - self.assertEqual(punr._pre_limits, 0) - # Shouldn't hit the DB yet. - self.assertEqual(punr._post_limits, 0) - self.assertEqual(len(json.loads(force_text(punr.get_list(request=empty_request).content))['objects']), 4) - - # Requests with an Anonymous user get no objects. - anony_bundle = punr.build_bundle(request=anony_request) - self.assertEqual(punr.authorized_read_list(punr.get_object_list(anony_request), anony_bundle).count(), 0) - self.assertEqual(len(json.loads(force_text(punr.get_list(request=anony_request).content))['objects']), 0) - - # Requests with an authenticated user get all objects for that user - # that are active. - authed_bundle = punr.build_bundle(request=authed_request) - self.assertEqual(punr.authorized_read_list(punr.get_object_list(authed_request), authed_bundle).count(), 2) - self.assertEqual(len(json.loads(force_text(punr.get_list(request=authed_request).content))['objects']), 2) - - # Demonstrate that a different user gets different objects. - authed_bundle_2 = punr.build_bundle(request=authed_request_2) - self.assertEqual(punr.authorized_read_list(punr.get_object_list(authed_request_2), authed_bundle_2).count(), 2) - self.assertEqual(len(json.loads(force_text(punr.get_list(request=authed_request_2).content))['objects']), 2) - self.assertEqual(list(punr.authorized_read_list(punr.get_object_list(authed_request), authed_bundle).values_list('id', flat=True)), [1, 2]) - self.assertEqual(list(punr.authorized_read_list(punr.get_object_list(authed_request_2), authed_bundle_2).values_list('id', flat=True)), [4, 6]) - - def test_per_object_authorization(self): - ponr = PerObjectNoteResource() - empty_request = HttpRequest() - empty_request.method = 'GET' - empty_request.GET = {'format': 'json'} - - self.assertEqual(ponr._meta.queryset.count(), 6) - empty_bundle = ponr.build_bundle(request=empty_request) - - # Should return only two objects with 'post' in the ``title``. - self.assertEqual(len(ponr.get_object_list(empty_request)), 6) - self.assertEqual(len(ponr.authorized_read_list(ponr.get_object_list(empty_request), empty_bundle)), 2) - self.assertEqual(ponr._pre_limits, 0) - # Since the objects weren't filtered, we hit everything. - self.assertEqual(ponr._post_limits, 6) - - self.assertEqual(len(json.loads(force_text(ponr.get_list(request=empty_request).content))['objects']), 2) - self.assertEqual(ponr._pre_limits, 0) - # Since the objects weren't filtered, we again hit everything. - self.assertEqual(ponr._post_limits, 6) - - empty_request.GET['is_active'] = True - self.assertEqual(len(json.loads(force_text(ponr.get_list(request=empty_request).content))['objects']), 2) - self.assertEqual(ponr._pre_limits, 0) - # This time, the objects were filtered, so we should only iterate over - # a (hopefully much smaller) subset. - self.assertEqual(ponr._post_limits, 4) - - def regression_test_per_object_detail(self): - ponr = PerObjectNoteResource() - empty_request = type('MockRequest', (object,), {'GET': {}}) - base_bundle = Bundle(request=empty_request) - - self.assertEqual(ponr._meta.queryset.count(), 6) - - # Regression: Make sure that simple ``get_detail`` requests work. - self.assertTrue(isinstance(ponr.obj_get(bundle=base_bundle, pk=1), Note)) - self.assertEqual(ponr.obj_get(bundle=base_bundle, pk=1).pk, 1) - self.assertEqual(ponr._pre_limits, 0) - self.assertEqual(ponr._post_limits, 1) - - with self.assertRaises(MultipleObjectsReturned) as ctx: - ponr.obj_get(bundle=base_bundle, is_active=True, pk__gte=1) - e = ctx.exception - self.assertEqual(str(e), "More than 'Note' matched 'is_active=True, pk__gte=1'.") - - with self.assertRaises(Note.DoesNotExist) as ctx: - ponr.obj_get(bundle=base_bundle, pk=1000000) - e = ctx.exception - self.assertEqual(str(e), "Couldn't find an instance of 'Note' which matched 'pk=1000000'.") - - def test_browser_cache(self): - resource = NoteResource() - request = MockRequest() - request.GET = {'format': 'json'} - - # First as a normal request. - resp = resource.wrap_view('dispatch_detail')(request, pk=1) - # resp = resource.get_detail(request, pk=1) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - self.assertTrue(resp.has_header('Cache-Control')) - self.assertEqual(resp._headers['cache-control'], ('Cache-Control', 'no-cache')) - - # Now as Ajax. - request.META = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} - resp = resource.wrap_view('dispatch_detail')(request, pk=1) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode('utf-8'), '{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - self.assertTrue(resp.has_header('cache-control')) - self.assertEqual(resp._headers['cache-control'], ('Cache-Control', 'no-cache')) - - def test_custom_paginator(self): - mock_request = MockRequest() - customs = CustomPageNoteResource().get_list(mock_request) - data = json.loads(customs.content.decode('utf-8')) - self.assertEqual(len(data), 3) - self.assertEqual(len(data['objects']), 6) - self.assertEqual(data['extra'], 'Some extra stuff here.') - - def test_readonly_full_hydrate(self): - rornr = ReadOnlyRelatedNoteResource() - note = Note.objects.get(pk=1) - dbundle = Bundle(obj=note) - - # Make sure the field is there on read. - dehydrated = rornr.full_dehydrate(dbundle) - self.assertTrue('author' in dehydrated.data) - - # Now check that it can be omitted in ``full_hydrate`` - hbundle = Bundle(obj=note, data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), - }) - hydrated = rornr.full_hydrate(hbundle) - self.assertEqual(hydrated.obj.author.username, 'johndoe') - - # It also shouldn't accept a new value & should silently ignore it. - hbundle_2 = Bundle(obj=note, data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), - 'author': '/api/v1/users/2/', - }) - hydrated_2 = rornr.full_hydrate(hbundle_2) - self.assertEqual(hydrated_2.obj.author.username, 'johndoe') - - def test_readonly_save_related(self): - rornr = ReadOnlyRelatedNoteResource() - note = Note.objects.get(pk=1) - dbundle = Bundle(obj=note) - - # Make sure the field is there on read. - dehydrated = rornr.full_dehydrate(dbundle) - self.assertTrue('author' in dehydrated.data) - - # Fetch the bundle - hbundle = Bundle(obj=note, data={ - 'name': 'Daniel', - 'view_count': 6, - 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), - 'author': '/api/v1/users/2/', - }) - hydrated = rornr.full_hydrate(hbundle) - - # Get the related object. - related_obj = getattr(hydrated.obj, "author") - - # Monkey Patch save to raise an exception - def fake_save(*args, **kwargs): - raise Exception("save() called in a readonly field") - - _real_save = related_obj.save - - try: - related_obj.save = fake_save - - rornr.save_related(hydrated) - finally: - related_obj.save = _real_save - - def test_collection_name(self): - resource = AlternativeCollectionNameNoteResource() - request = HttpRequest() - response = resource.get_list(request) - response_data = json.loads(force_text(response.content)) - self.assertTrue('alt_objects' in response_data) - - def test_collection_name_patch_list(self): - """Test that patch list accepts alternative names""" - resource = AlternativeCollectionNameNoteResource() - request = HttpRequest() - request._body = request._raw_post_data = json.dumps({ - 'alt_objects_delete': [], - 'alt_objects': [{'title': 'Testing'}] - }) - request._read_started = False - - response = resource.patch_list(request) - self.assertEqual(response.status_code, 202) - - -class BasicAuthResourceTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def test_dispatch_list(self): - resource = BasicAuthNoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - with self.assertRaises(ImmediateHttpResponse) as ctx: - resp = resource.dispatch_list(request) - e = ctx.exception - self.assertEqual(e.response.status_code, 401) - - # Try again with ``wrap_view`` for sanity. - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp.status_code, 401) - - john_doe = User.objects.get(username='johndoe') - john_doe.set_password('pass') - john_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('johndoe:pass'.encode('utf-8')).decode('utf-8') - - resp = resource.dispatch_list(request) - self.assertEqual(resp.status_code, 200) - - def test_dispatch_detail(self): - resource = BasicAuthNoteResource() - request = HttpRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - with self.assertRaises(ImmediateHttpResponse) as ctx: - resource.dispatch_detail(request, pk=1) - e = ctx.exception - self.assertEqual(e.response.status_code, 401) - - # Try again with ``wrap_view`` for sanity. - resp = resource.wrap_view('dispatch_detail')(request, pk=1) - self.assertEqual(resp.status_code, 401) - - john_doe = User.objects.get(username='johndoe') - john_doe.set_password('pass') - john_doe.save() - request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('johndoe:pass'.encode('utf-8')).decode('utf-8') - - resp = resource.dispatch_list(request) - self.assertEqual(resp.status_code, 200) - - -# Test out the 500 behavior. -class YouFail(Exception): - pass - - -class YouFailWithResponseAttr(Exception): - response = None - - -class BustedResource(BasicResource): - err_class = YouFail - - def get_list(self, request, **kwargs): - raise self.err_class("Something blew up.") - - def get_detail(self, request, **kwargs): - raise NotFound("It's just not there.") - - def post_list(self, request, **kwargs): - raise Http404("Not here either") - - def post_detail(self, request, **kwargs): - raise self.err_class("") - - -@override_settings(TASTYPIE_FULL_DEBUG=False, TASTYPIE_CANNED_ERROR="Sorry, this request could not be processed. Please try again later.") -class BustedResourceTestCase(TestCase): - def setUp(self): - super(BustedResourceTestCase, self).setUp() - - self.resource = BustedResource() - self.request = HttpRequest() - self.request.GET = {'format': 'json'} - self.request.method = 'GET' - - @override_settings(DEBUG=True, TASTYPIE_FULL_DEBUG=True) - def test_debug_on_with_full(self): - with self.assertRaises(self.resource.err_class): - self.resource.wrap_view('get_list')(self.request, pk=1) - - @override_settings(DEBUG=True, TASTYPIE_FULL_DEBUG=False) - def test_debug_on_without_full(self): - mail.outbox = [] - - resp = self.resource.wrap_view('get_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 500) - content = json.loads(resp.content.decode('utf-8')) - self.assertEqual(content['error_message'], 'Something blew up.') - self.assertTrue(len(content['traceback']) > 0) - self.assertEqual(len(mail.outbox), 0) - - @override_settings(DEBUG=False, TASTYPIE_FULL_DEBUG=False) - def test_debug_off(self): - SimpleHandler.logged = [] - - resp = self.resource.wrap_view('get_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 500) - self.assertEqual(resp.content.decode('utf-8'), '{"error_message": "Sorry, this request could not be processed. Please try again later."}') - self.assertEqual(len(SimpleHandler.logged), 1) - - # Ensure that 404s don't send email. - resp = self.resource.wrap_view('get_detail')(self.request, pk=10000000) - self.assertEqual(resp.status_code, 404) - self.assertEqual(resp.content.decode('utf-8'), '{"error_message": "Sorry, this request could not be processed. Please try again later."}') - self.assertEqual(len(SimpleHandler.logged), 1) - SimpleHandler.logged = [] - - @override_settings(DEBUG=False, TASTYPIE_FULL_DEBUG=False, TASTYPIE_CANNED_ERROR="Oops, you bwoke it.") - def test_debug_off_custom_message(self): - SimpleHandler.logged = [] - - resp = self.resource.wrap_view('get_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 500) - self.assertEqual(resp.content.decode('utf-8'), '{"error_message": "Oops, you bwoke it."}') - self.assertEqual(len(SimpleHandler.logged), 1) - SimpleHandler.logged = [] - - def test_http404_raises_404(self): - self.request.method = 'POST' - resp = self.resource.wrap_view('post_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 404) - - @override_settings(DEBUG=True, TASTYPIE_FULL_DEBUG=False) - def test_escaping(self): - request = HttpRequest() - request.method = 'POST' - request.POST = { - 'whatever': 'stuff', - } - res = self.resource.wrap_view('dispatch_detail')(request, pk=1) - self.assertEqual(res.status_code, 500) - err_data = json.loads(res.content.decode('utf-8')) - self.assertTrue('<script>alert(1)</script>' in err_data['error_message']) - - -@override_settings(TASTYPIE_FULL_DEBUG=False, TASTYPIE_CANNED_ERROR="Sorry, this request could not be processed. Please try again later.") -class BustedResourceWithNoneResponseErrorAttrTestCase(TestCase): - def setUp(self): - super(BustedResourceWithNoneResponseErrorAttrTestCase, self).setUp() - - self.resource = BustedResource() - self.resource.err_class = YouFailWithResponseAttr - self.request = HttpRequest() - self.request.GET = {'format': 'json'} - self.request.method = 'GET' - - @override_settings(DEBUG=True, TASTYPIE_FULL_DEBUG=True) - def test_debug_on_with_full(self): - with self.assertRaises(self.resource.err_class): - self.resource.wrap_view('get_list')(self.request, pk=1) - - @override_settings(DEBUG=True, TASTYPIE_FULL_DEBUG=False) - def test_debug_on_without_full(self): - mail.outbox = [] - - resp = self.resource.wrap_view('get_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 500) - content = json.loads(resp.content.decode('utf-8')) - self.assertEqual(content['error_message'], 'Something blew up.') - self.assertTrue(len(content['traceback']) > 0) - self.assertEqual(len(mail.outbox), 0) - - @override_settings(DEBUG=False, TASTYPIE_FULL_DEBUG=False) - def test_debug_off(self): - SimpleHandler.logged = [] - - resp = self.resource.wrap_view('get_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 500) - self.assertEqual(resp.content.decode('utf-8'), '{"error_message": "Sorry, this request could not be processed. Please try again later."}') - self.assertEqual(len(SimpleHandler.logged), 1) - - # Ensure that 404s don't send email. - resp = self.resource.wrap_view('get_detail')(self.request, pk=10000000) - self.assertEqual(resp.status_code, 404) - self.assertEqual(resp.content.decode('utf-8'), '{"error_message": "Sorry, this request could not be processed. Please try again later."}') - self.assertEqual(len(SimpleHandler.logged), 1) - SimpleHandler.logged = [] - - @override_settings(DEBUG=False, TASTYPIE_FULL_DEBUG=False, TASTYPIE_CANNED_ERROR="Oops, you bwoke it.") - def test_debug_off_custom_message(self): - SimpleHandler.logged = [] - - resp = self.resource.wrap_view('get_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 500) - self.assertEqual(resp.content.decode('utf-8'), '{"error_message": "Oops, you bwoke it."}') - self.assertEqual(len(SimpleHandler.logged), 1) - SimpleHandler.logged = [] - - def test_http404_raises_404(self): - self.request.method = 'POST' - resp = self.resource.wrap_view('post_list')(self.request, pk=1) - self.assertEqual(resp.status_code, 404) - - @override_settings(DEBUG=True, TASTYPIE_FULL_DEBUG=False) - def test_escaping(self): - request = HttpRequest() - request.method = 'POST' - request.POST = { - 'whatever': 'stuff', - } - res = self.resource.wrap_view('dispatch_detail')(request, pk=1) - self.assertEqual(res.status_code, 500) - err_data = json.loads(res.content.decode('utf-8')) - self.assertTrue('<script>alert(1)</script>' in err_data['error_message']) - - -class ObjectlessResource(Resource): - test = fields.CharField(default='objectless_test') - - class Meta: - resource_name = 'objectless' - - -class ObjectlessResourceTestCase(TestCase): - def test_build_bundle(self): - resource = ObjectlessResource() - - bundle = resource.build_bundle() - - self.assertTrue(bundle is not None) - - -class Handle500TestCase(TestCase): - - def setUp(self): - self.resource = Resource() - self.resource.error_response = Mock() - self.request = Mock() - - @override_settings(DEBUG=True) - @patch('tastypie.resources.traceback') - def test_unsupported_format_debug(self, traceback): - traceback.format_exception = Mock(return_value=[]) - - msg = 'Unknown format message' - exc = UnsupportedFormat(msg) - - self.resource._handle_500(self.request, exc) - - self.assertEqual(self.resource.error_response.call_count, 1) - - args, kwargs = self.resource.error_response.call_args - self.assertEqual(args[1]['error_message'], msg) - self.assertEqual(kwargs['response_class'], http.HttpBadRequest) - - @override_settings(DEBUG=False) - @patch('tastypie.resources.traceback') - def test_unsupported_format_no_debug(self, traceback): - traceback.format_exception = Mock(return_value=[]) - - msg = 'Unknown format message' - exc = UnsupportedFormat(msg) - - self.resource._handle_500(self.request, exc) - - self.assertEqual(self.resource.error_response.call_count, 1) - - args, kwargs = self.resource.error_response.call_args - self.assertEqual(kwargs['response_class'], http.HttpBadRequest) diff -Nru django-tastypie-0.13.3/tests/core/tests/resource_urls.py django-tastypie-0.14.3/tests/core/tests/resource_urls.py --- django-tastypie-0.13.3/tests/core/tests/resource_urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/resource_urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -from django.conf.urls import include, url -from django.contrib.auth.models import User -from tastypie import fields -from tastypie.resources import ModelResource -from core.models import Note, Subject -from core.tests.api import Api - - -class SubjectResource(ModelResource): - class Meta: - resource_name = 'subjects' - queryset = Subject.objects.all() - - -class UserResource(ModelResource): - class Meta: - resource_name = 'user' - queryset = User.objects.all() - - -class CustomNoteResource(ModelResource): - author = fields.ForeignKey(UserResource, 'author') - subjects = fields.ManyToManyField(SubjectResource, 'subjects') - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - - -api = Api(api_name='v1') -api.register(CustomNoteResource()) -api.register(UserResource()) -api.register(SubjectResource()) - -urlpatterns = [ - url(r'^api/', include(api.urls)), -] diff -Nru django-tastypie-0.13.3/tests/core/tests/serializers.py django-tastypie-0.14.3/tests/core/tests/serializers.py --- django-tastypie-0.13.3/tests/core/tests/serializers.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/serializers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,522 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from decimal import Decimal -from unittest import skipIf -import yaml - -from django.core.exceptions import ImproperlyConfigured -from django.test import TestCase - -from tastypie.bundle import Bundle -from tastypie import fields -from tastypie.exceptions import BadRequest -from tastypie.serializers import Serializer -from tastypie.resources import ModelResource - -from core.models import Note - -try: - import biplist -except ImportError: - biplist = None - - -skipIfNoBiplist = skipIf(biplist is None, 'biplist not present') - - -class UnsafeObject(object): - pass - - -class NoteResource(ModelResource): - class Meta: - resource_name = 'notes' - queryset = Note.objects.filter(is_active=True) - - -class AnotherNoteResource(ModelResource): - aliases = fields.ListField(attribute='aliases', null=True) - meta = fields.DictField(attribute='metadata', null=True) - owed = fields.DecimalField(attribute='money_owed', null=True) - - class Meta: - resource_name = 'anothernotes' - queryset = Note.objects.filter(is_active=True) - - def dehydrate(self, bundle): - bundle.data['aliases'] = ['Mr. Smith', 'John Doe'] - bundle.data['meta'] = {'threat': 'high'} - bundle.data['owed'] = Decimal('102.57') - return bundle - - -class SerializerTestCase(TestCase): - def test_init(self): - serializer_1 = Serializer() - self.assertEqual(serializer_1.formats, ['json', 'xml', 'yaml', 'plist']) - self.assertEqual(serializer_1.content_types, {'xml': 'application/xml', 'yaml': 'text/yaml', 'json': 'application/json', 'jsonp': 'text/javascript', 'plist': 'application/x-plist'}) - self.assertEqual(serializer_1.supported_formats, ['application/json', 'application/xml', 'text/yaml', 'application/x-plist']) - - serializer_2 = Serializer(formats=['json', 'xml']) - self.assertEqual(serializer_2.formats, ['json', 'xml']) - self.assertEqual(serializer_2.content_types, {'xml': 'application/xml', 'yaml': 'text/yaml', 'json': 'application/json', 'jsonp': 'text/javascript', 'plist': 'application/x-plist'}) - self.assertEqual(serializer_2.supported_formats, ['application/json', 'application/xml']) - - serializer_3 = Serializer(formats=['json', 'xml'], content_types={'json': 'text/json', 'xml': 'application/xml'}) - self.assertEqual(serializer_3.formats, ['json', 'xml']) - self.assertEqual(serializer_3.content_types, {'xml': 'application/xml', 'json': 'text/json'}) - self.assertEqual(serializer_3.supported_formats, ['text/json', 'application/xml']) - - serializer_4 = Serializer(formats=['plist', 'json'], content_types={'plist': 'application/x-plist', 'json': 'application/json'}) - self.assertEqual(serializer_4.formats, ['plist', 'json']) - self.assertEqual(serializer_4.content_types, {'plist': 'application/x-plist', 'json': 'application/json'}) - self.assertEqual(serializer_4.supported_formats, ['application/x-plist', 'application/json']) - - self.assertRaises(ImproperlyConfigured, Serializer, formats=['json', 'xml'], content_types={'json': 'text/json'}) - - def test_default_formats_setting(self): - with self.settings(TASTYPIE_DEFAULT_FORMATS=('json', 'xml')): - # Confirm that the setting will override the default values: - s = Serializer() - self.assertEqual(list(s.formats), ['json', 'xml']) - self.assertEqual(list(s.supported_formats), ['application/json', 'application/xml']) - self.assertEqual(s.content_types, {'xml': 'application/xml', 'yaml': 'text/yaml', 'json': 'application/json', 'jsonp': 'text/javascript', 'plist': 'application/x-plist'}) - - # Confirm that subclasses which set their own formats list won't be overriden: - class JSONSerializer(Serializer): - formats = ['json'] - - js = JSONSerializer() - self.assertEqual(list(js.formats), ['json']) - self.assertEqual(list(js.supported_formats), ['application/json']) - - def get_sample1(self): - return { - 'name': 'Daniel', - 'age': 27, - 'date_joined': datetime.date(2010, 3, 27), - 'snowman': u'☃', - } - - def get_sample2(self): - return { - 'somelist': ['hello', 1, None], - 'somehash': {'pi': 3.14, 'foo': 'bar'}, - 'somestring': 'hello', - 'true': True, - 'false': False, - } - - def test__to_simple__none(self): - serializer = Serializer() - val = None - self.assertIs(serializer.to_simple(val, None), val) - - def test__to_simple__float(self): - serializer = Serializer() - val = 1.0 - self.assertIs(serializer.to_simple(val, None), val) - - def test__to_simple__int(self): - serializer = Serializer() - val = 1 - self.assertIs(serializer.to_simple(val, None), val) - - def test__to_simple__bool(self): - serializer = Serializer() - val = True - self.assertIs(serializer.to_simple(val, None), val) - - def test__to_simple__dict(self): - serializer = Serializer() - val = {'foo': True} - self.assertEqual(serializer.to_simple(val, None), {'foo': True}) - - def test__to_simple__list(self): - serializer = Serializer() - val = [True] - self.assertEqual(serializer.to_simple(val, None), [True]) - - def test__to_simple__tuple(self): - serializer = Serializer() - val = (True,) - self.assertEqual(serializer.to_simple(val, None), [True]) - - def test__to_simple__bundle(self): - serializer = Serializer() - val = Bundle(data={'foo': True}) - self.assertEqual(serializer.to_simple(val, None), {'foo': True}) - - def test__to_simple__string(self): - serializer = Serializer() - val = b"\xc3\xa1hhh! I'm letting all the \xc3\xa1's out of my body." - self.assertEqual(serializer.to_simple(val, None), u"áhhh! I'm letting all the á's out of my body.") - - def test__to_simple__datetime(self): - serializer = Serializer() - self.assertEqual(serializer.to_simple(datetime.datetime(2010, 12, 16, 2, 31, 33), None), '2010-12-16T02:31:33') - - def test__to_simple__date(self): - serializer = Serializer() - self.assertEqual(serializer.to_simple(datetime.date(2010, 12, 16), None), '2010-12-16') - - def test__to_simple__time(self): - serializer = Serializer() - self.assertEqual(serializer.to_simple(datetime.time(2, 31, 33), None), '02:31:33') - - def test_format_datetime(self): - serializer = Serializer() - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), '2010-12-16T02:31:33') - - serializer = Serializer(datetime_formatting='iso-8601') - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), '2010-12-16T02:31:33') - - serializer = Serializer(datetime_formatting='iso-8601-strict') - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33, 10)), '2010-12-16T02:31:33') - - serializer = Serializer(datetime_formatting='rfc-2822') - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), u'Thu, 16 Dec 2010 02:31:33 -0600') - - serializer = Serializer(datetime_formatting='random-garbage') - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), '2010-12-16T02:31:33') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='iso-8601'): - serializer = Serializer() - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), '2010-12-16T02:31:33') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='iso-8601-strict'): - serializer = Serializer() - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33, 10)), '2010-12-16T02:31:33') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='rfc-2822'): - serializer = Serializer() - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), u'Thu, 16 Dec 2010 02:31:33 -0600') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='random-garbage'): - serializer = Serializer() - self.assertEqual(serializer.format_datetime(datetime.datetime(2010, 12, 16, 2, 31, 33)), '2010-12-16T02:31:33') - - def test_format_date(self): - serializer = Serializer() - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), '2010-12-16') - - serializer = Serializer(datetime_formatting='iso-8601') - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), '2010-12-16') - - serializer = Serializer(datetime_formatting='rfc-2822') - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), u'16 Dec 2010') - - serializer = Serializer(datetime_formatting='random-garbage') - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), '2010-12-16') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='iso-8601'): - serializer = Serializer() - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), '2010-12-16') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='rfc-2822'): - serializer = Serializer() - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), u'16 Dec 2010') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='random-garbage'): - serializer = Serializer() - self.assertEqual(serializer.format_date(datetime.date(2010, 12, 16)), '2010-12-16') - - def test_format_time(self): - serializer = Serializer() - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), '02:31:33') - - serializer = Serializer(datetime_formatting='iso-8601') - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), '02:31:33') - - serializer = Serializer(datetime_formatting='iso-8601-strict') - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33, 10)), '02:31:33') - - serializer = Serializer(datetime_formatting='rfc-2822') - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), u'02:31:33 -0600') - - serializer = Serializer(datetime_formatting='random-garbage') - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), '02:31:33') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='iso-8601'): - serializer = Serializer() - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), '02:31:33') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='iso-8601-strict'): - serializer = Serializer() - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33, 10)), '02:31:33') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='rfc-2822'): - serializer = Serializer() - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), u'02:31:33 -0600') - - with self.settings(TASTYPIE_DATETIME_FORMATTING='random-garbage'): - serializer = Serializer() - self.assertEqual(serializer.format_time(datetime.time(2, 31, 33)), '02:31:33') - - def test_to_xml(self): - serializer = Serializer() - sample_1 = self.get_sample1() - # This needs a little explanation. - # From http://lxml.de/parsing.html, what comes out of ``tostring`` - # (despite encoding as UTF-8) is a bytestring. This is because that's - # what other libraries expect (& will do the decode). We decode here - # so we can make extra special sure it looks right. - binary_xml = serializer.to_xml(sample_1) - unicode_xml = binary_xml.decode('utf-8') - self.assertEqual(unicode_xml, u'\n272010-03-27Daniel') - - def test_to_xml2(self): - serializer = Serializer() - sample_2 = self.get_sample2() - binary_xml = serializer.to_xml(sample_2) - unicode_xml = binary_xml.decode('utf-8') - self.assertEqual(unicode_xml, '\nFalsebar3.14hello1helloTrue') - - def test_from_xml(self): - serializer = Serializer() - data = u'\n27Daniel2010-03-27True' - self.assertEqual(serializer.from_xml(data), {'rocksdahouse': True, 'age': 27, 'name': 'Daniel', 'date_joined': '2010-03-27', 'snowman': u'☃'}) - - def test_from_xml2(self): - serializer = Serializer() - data = '\nhello13.14barFalseTruehello' - self.assertEqual(serializer.from_xml(data), self.get_sample2()) - - def test_malformed_xml(self): - serializer = Serializer() - data = '\n]> - &a; - """ - self.assertRaises(BadRequest, serializer.from_xml, data) - - def test_to_jsonp(self): - serializer = Serializer() - - sample_1 = self.get_sample1() - options = {'callback': 'myCallback'} - serialized = serializer.to_jsonp(sample_1, options=options) - serialized_json = serializer.to_json(sample_1) - self.assertEqual('myCallback(%s)' % serialized_json, - serialized) - - def test_invalid_jsonp_characters(self): - """ - The newline characters \u2028 and \u2029 need to be escaped - in JSONP. - """ - serializer = Serializer() - - jsonp = serializer.to_jsonp({'foo': u'Hello \u2028\u2029world!'}, - {'callback': 'callback'}) - self.assertEqual(jsonp, u'callback({"foo": "Hello \\u2028\\u2029world!"})') - - @skipIfNoBiplist - def test_to_plist(self): - serializer = Serializer() - - sample_1 = self.get_sample1() - self.assertTrue(serializer.to_plist(sample_1).startswith(b'bplist00bybiplist1.0')) - - @skipIfNoBiplist - def test_from_plist(self): - serializer = Serializer() - - sample_1 = serializer.from_plist(b'bplist00bybiplist1.0\x00\xd4\x01\x02\x03\x04\x05\x06\x07\x08WsnowmanSageTname[date_joineda&\x03\x10\x1bf\x00D\x00a\x00n\x00i\x00e\x00lZ2010-03-27\x15\x1e&*/;>@M\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X') - self.assertEqual(len(sample_1), 4) - self.assertEqual(sample_1['name'], 'Daniel') - self.assertEqual(sample_1['age'], 27) - self.assertEqual(sample_1['date_joined'], '2010-03-27') - self.assertEqual(sample_1['snowman'], u'☃') - - -class ResourceSerializationTestCase(TestCase): - fixtures = ['note_testdata.json'] - - def setUp(self): - super(ResourceSerializationTestCase, self).setUp() - self.resource = NoteResource() - base_bundle = Bundle() - self.obj_list = [self.resource.full_dehydrate(self.resource.build_bundle(obj=obj)) for obj in self.resource.obj_get_list(base_bundle)] - self.another_resource = AnotherNoteResource() - self.another_obj_list = [self.another_resource.full_dehydrate(self.resource.build_bundle(obj=obj)) for obj in self.another_resource.obj_get_list(base_bundle)] - - def test_to_xml_multirepr(self): - serializer = Serializer() - binary_xml = serializer.to_xml(self.obj_list) - unicode_xml = binary_xml.decode('utf-8') - self.assertEqual(unicode_xml, '\nThis is my very first post using my shiny new API. Pretty sweet, huh?2010-03-30T20:05:001Truefirst-postFirst Post!2010-03-30T20:05:00The dog ate my cat today. He looks seriously uncomfortable.2010-03-31T20:05:002Trueanother-postAnother Post2010-03-31T20:05:00My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.2010-04-01T20:05:004Truerecent-volcanic-activityRecent Volcanic Activity.2010-04-01T20:05:00Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!2010-04-02T10:05:006Truegrannys-goneGranny\'s Gone2010-04-02T10:05:00') - - def test_to_xml_single(self): - serializer = Serializer() - resource = self.obj_list[0] - binary_xml = serializer.to_xml(resource) - unicode_xml = binary_xml.decode('utf-8') - self.assertEqual(unicode_xml, '\nThis is my very first post using my shiny new API. Pretty sweet, huh?2010-03-30T20:05:001Truefirst-postFirst Post!2010-03-30T20:05:00') - - def test_to_xml_nested(self): - serializer = Serializer() - resource = self.obj_list[0] - data = { - 'stuff': { - 'foo': 'bar', - 'object': resource, - } - } - binary_xml = serializer.to_xml(data) - unicode_xml = binary_xml.decode('utf-8') - self.assertEqual(unicode_xml, '\nbarThis is my very first post using my shiny new API. Pretty sweet, huh?2010-03-30T20:05:001Truefirst-postFirst Post!2010-03-30T20:05:00') - - def test_to_json_multirepr(self): - serializer = Serializer() - self.assertEqual(serializer.to_json(self.obj_list), '[{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": 4, "is_active": true, "resource_uri": "", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": 6, "is_active": true, "resource_uri": "", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}]') - - def test_to_json_single(self): - serializer = Serializer() - resource = self.obj_list[0] - self.assertEqual(serializer.to_json(resource), '{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - - def test_to_json_decimal_list_dict(self): - serializer = Serializer() - resource = self.another_obj_list[0] - self.assertEqual(serializer.to_json(resource), '{"aliases": ["Mr. Smith", "John Doe"], "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "meta": {"threat": "high"}, "owed": "102.57", "resource_uri": "", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}') - - def test_to_json_nested(self): - serializer = Serializer() - resource = self.obj_list[0] - data = { - 'stuff': { - 'foo': 'bar', - 'object': resource, - } - } - self.assertEqual(serializer.to_json(data), '{"stuff": {"foo": "bar", "object": {"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}}}') - - -class StubbedSerializer(Serializer): - def __init__(self, *args, **kwargs): - super(StubbedSerializer, self).__init__(*args, **kwargs) - self.from_json_called = False - self.from_xml_called = False - self.from_yaml_called = False - self.from_jsonp_called = False - - def from_json(self, data): - self.from_json_called = True - return True - - def from_xml(self, data): - self.from_xml_called = True - return True - - def from_yaml(self, data): - self.from_yaml_called = True - return True - - def from_jsonp(self, data): - self.from_jsonp_called = True - return True - - -class ContentHeaderTest(TestCase): - def test_deserialize_json(self): - serializer = StubbedSerializer() - serializer.deserialize('{}', 'application/json') - self.assertTrue(serializer.from_json_called) - - def test_deserialize_json_with_charset(self): - serializer = StubbedSerializer() - serializer.deserialize('{}', 'application/json; charset=UTF-8') - self.assertTrue(serializer.from_json_called) - - def test_deserialize_xml(self): - serializer = StubbedSerializer() - serializer.deserialize('', 'application/xml') - self.assertTrue(serializer.from_xml_called) - - def test_deserialize_xml_with_charset(self): - serializer = StubbedSerializer() - serializer.deserialize('', 'application/xml; charset=UTF-8') - self.assertTrue(serializer.from_xml_called) - - def test_deserialize_yaml(self): - serializer = StubbedSerializer() - serializer.deserialize('', 'text/yaml') - self.assertTrue(serializer.from_yaml_called) - - def test_deserialize_yaml_with_charset(self): - serializer = StubbedSerializer() - serializer.deserialize('', 'text/yaml; charset=UTF-8') - self.assertTrue(serializer.from_yaml_called) - - def test_deserialize_jsonp(self): - serializer = StubbedSerializer() - serializer.deserialize('{}', 'text/javascript') - self.assertTrue(serializer.from_jsonp_called) - - def test_deserialize_jsonp_with_charset(self): - serializer = StubbedSerializer() - serializer.deserialize('{}', 'text/javascript; charset=UTF-8') - self.assertTrue(serializer.from_jsonp_called) diff -Nru django-tastypie-0.13.3/tests/core/tests/throttle.py django-tastypie-0.14.3/tests/core/tests/throttle.py --- django-tastypie-0.13.3/tests/core/tests/throttle.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/throttle.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,147 +0,0 @@ -import mock -import time - -from django.core.cache import cache -from django.test import TestCase -from django.utils.encoding import force_text - -from tastypie.models import ApiAccess -from tastypie.throttle import BaseThrottle, CacheThrottle, CacheDBThrottle - - -class NoThrottleTestCase(TestCase): - def test_init(self): - throttle_1 = BaseThrottle() - self.assertEqual(throttle_1.throttle_at, 150) - self.assertEqual(throttle_1.timeframe, 3600) - self.assertEqual(throttle_1.expiration, 604800) - - throttle_2 = BaseThrottle(throttle_at=50, timeframe=60 * 30, expiration=1) - self.assertEqual(throttle_2.throttle_at, 50) - self.assertEqual(throttle_2.timeframe, 1800) - self.assertEqual(throttle_2.expiration, 1) - - def test_convert_identifier_to_key(self): - throttle_1 = BaseThrottle() - self.assertEqual(throttle_1.convert_identifier_to_key(''), '_accesses') - self.assertEqual(throttle_1.convert_identifier_to_key('alnum10'), 'alnum10_accesses') - self.assertEqual(throttle_1.convert_identifier_to_key('Mr. Pants'), 'Mr.Pants_accesses') - self.assertEqual(throttle_1.convert_identifier_to_key('Mr_Pants'), 'Mr_Pants_accesses') - self.assertEqual(throttle_1.convert_identifier_to_key('%^@@$&!a'), 'a_accesses') - - def test_should_be_throttled(self): - throttle_1 = BaseThrottle() - self.assertEqual(throttle_1.should_be_throttled('foobaz'), False) - - def test_accessed(self): - throttle_1 = BaseThrottle() - self.assertEqual(throttle_1.accessed('foobaz'), None) - - -@mock.patch('tastypie.throttle.time') -class CacheThrottleTestCase(TestCase): - def tearDown(self): - cache.clear() - - def test_throttling(self, mocked_time): - mocked_time.time.return_value = time.time() - - throttle_1 = CacheThrottle(throttle_at=2, timeframe=5, expiration=2) - - self.assertEqual(throttle_1.should_be_throttled('daniel'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 0) - self.assertEqual(throttle_1.accessed('daniel'), None) - - self.assertEqual(throttle_1.should_be_throttled('daniel'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 1) - self.assertEqual(cache.get('cody_accesses'), None) - self.assertEqual(throttle_1.accessed('daniel'), None) - - self.assertEqual(throttle_1.accessed('cody'), None) - self.assertEqual(throttle_1.should_be_throttled('cody'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 2) - self.assertEqual(len(cache.get('cody_accesses')), 1) - - # THROTTLE'D! - self.assertEqual(throttle_1.should_be_throttled('daniel'), 5) - self.assertEqual(len(cache.get('daniel_accesses')), 2) - self.assertEqual(throttle_1.accessed('daniel'), None) - - self.assertEqual(throttle_1.should_be_throttled('daniel'), 5) - self.assertEqual(len(cache.get('daniel_accesses')), 3) - self.assertEqual(throttle_1.accessed('daniel'), None) - - # Should be no interplay. - self.assertEqual(throttle_1.should_be_throttled('cody'), False) - self.assertEqual(throttle_1.accessed('cody'), None) - - # Test the timeframe. - mocked_time.time.return_value += throttle_1.timeframe + 1 - - self.assertEqual(throttle_1.should_be_throttled('daniel'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 0) - - -@mock.patch('tastypie.throttle.time') -class CacheDBThrottleTestCase(TestCase): - def tearDown(self): - cache.clear() - - def test_throttling(self, mocked_time): - mocked_time.time.return_value = time.time() - - throttle_1 = CacheDBThrottle(throttle_at=2, timeframe=5, expiration=2) - - self.assertEqual(throttle_1.should_be_throttled('daniel'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 0) - self.assertEqual(ApiAccess.objects.count(), 0) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 0) - self.assertEqual(throttle_1.accessed('daniel'), None) - - self.assertEqual(throttle_1.should_be_throttled('daniel'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 1) - self.assertEqual(cache.get('cody_accesses'), None) - self.assertEqual(ApiAccess.objects.count(), 1) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 1) - self.assertEqual(throttle_1.accessed('daniel'), None) - - self.assertEqual(throttle_1.accessed('cody'), None) - self.assertEqual(throttle_1.should_be_throttled('cody'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 2) - self.assertEqual(len(cache.get('cody_accesses')), 1) - self.assertEqual(ApiAccess.objects.count(), 3) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 2) - self.assertEqual(throttle_1.accessed('cody'), None) - - # THROTTLE'D! - self.assertEqual(throttle_1.accessed('daniel'), None) - self.assertEqual(throttle_1.should_be_throttled('daniel'), 5) - self.assertEqual(len(cache.get('daniel_accesses')), 3) - self.assertEqual(ApiAccess.objects.count(), 5) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 3) - - self.assertEqual(throttle_1.accessed('daniel'), None) - self.assertEqual(throttle_1.should_be_throttled('daniel'), 5) - self.assertEqual(len(cache.get('daniel_accesses')), 4) - self.assertEqual(ApiAccess.objects.count(), 6) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 4) - - # Should be no interplay. - self.assertEqual(throttle_1.should_be_throttled('cody'), 5) - self.assertEqual(throttle_1.accessed('cody'), None) - self.assertEqual(ApiAccess.objects.count(), 7) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 4) - - # Test the timeframe. - mocked_time.time.return_value += throttle_1.timeframe + 1 - - self.assertEqual(throttle_1.should_be_throttled('daniel'), False) - self.assertEqual(len(cache.get('daniel_accesses')), 0) - self.assertEqual(ApiAccess.objects.count(), 7) - self.assertEqual(ApiAccess.objects.filter(identifier='daniel').count(), 4) - - -class ModelTestCase(TestCase): - def test_unicode(self): - access = ApiAccess(identifier="testing", accessed=0) - self.assertEqual(force_text(access), 'testing @ 0') diff -Nru django-tastypie-0.13.3/tests/core/tests/utils.py django-tastypie-0.14.3/tests/core/tests/utils.py --- django-tastypie-0.13.3/tests/core/tests/utils.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/utils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,126 +0,0 @@ -import datetime -import mock -from pytz.reference import Pacific - -from django.http import HttpRequest -from django.test import TestCase - -from tastypie.exceptions import BadRequest -from tastypie.serializers import Serializer -from tastypie.utils.mime import determine_format, build_content_type -from tastypie.utils.urls import trailing_slash -from tastypie.utils.timezone import now - - -class TrailingSlashTestCase(TestCase): - def test(self): - self.assertEqual(trailing_slash, '/') - - def test_callable(self): - self.assertEqual(trailing_slash(), '/') - - -class MimeTestCase(TestCase): - def test_build_content_type(self): - # JSON & JSONP don't include charset. - self.assertEqual(build_content_type('application/json'), 'application/json') - self.assertEqual(build_content_type('text/javascript'), 'text/javascript') - self.assertEqual(build_content_type('application/json', encoding='ascii'), 'application/json') - # Everything else should. - self.assertEqual(build_content_type('application/xml'), 'application/xml; charset=utf-8') - self.assertEqual(build_content_type('application/xml', encoding='ascii'), 'application/xml; charset=ascii') - - def test_determine_format(self): - serializer = Serializer() - full_serializer = Serializer(formats=['json', 'jsonp', 'xml', 'yaml', 'plist']) - request = HttpRequest() - - # Default. - self.assertEqual(determine_format(request, serializer), 'application/json') - - # Test forcing the ``format`` parameter. - request.GET = {'format': 'json'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - # Disabled by default. - request.GET = {'format': 'jsonp'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - # Explicitly enabled. - request.GET = {'format': 'jsonp'} - self.assertEqual(determine_format(request, full_serializer), 'text/javascript') - - request.GET = {'format': 'xml'} - self.assertEqual(determine_format(request, serializer), 'application/xml') - - request.GET = {'format': 'yaml'} - self.assertEqual(determine_format(request, serializer), 'text/yaml') - - request.GET = {'format': 'plist'} - self.assertEqual(determine_format(request, serializer), 'application/x-plist') - - request.GET = {'format': 'foo'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - # Test the ``Accept`` header. - request.META = {'HTTP_ACCEPT': 'application/json'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - # Again, disabled by default. - request.META = {'HTTP_ACCEPT': 'text/javascript'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - # Again, explicitly enabled. - request.META = {'HTTP_ACCEPT': 'text/javascript'} - self.assertEqual(determine_format(request, full_serializer), 'text/javascript') - - request.META = {'HTTP_ACCEPT': 'application/xml'} - self.assertEqual(determine_format(request, serializer), 'application/xml') - - request.META = {'HTTP_ACCEPT': 'text/yaml'} - self.assertEqual(determine_format(request, serializer), 'text/yaml') - - request.META = {'HTTP_ACCEPT': 'application/x-plist'} - self.assertEqual(determine_format(request, serializer), 'application/x-plist') - - # unsupported text/html - request.META = {'HTTP_ACCEPT': 'text/html'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - # unsupported binary - request.META = {'HTTP_ACCEPT': 'application/octet-stream'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - request.META = {'HTTP_ACCEPT': '*/*'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - request.META = {'HTTP_ACCEPT': 'application/json,application/xml;q=0.9,*/*;q=0.8'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - request.META = {'HTTP_ACCEPT': 'text/plain,application/xml,application/json;q=0.9,*/*;q=0.8'} - self.assertEqual(determine_format(request, serializer), 'application/xml') - - request.META = {'HTTP_ACCEPT': 'application/json; charset=UTF-8'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - request.META = {'HTTP_ACCEPT': 'text/javascript,application/json'} - self.assertEqual(determine_format(request, serializer), 'application/json') - - request.META = {'HTTP_ACCEPT': 'bogon'} - self.assertRaises(BadRequest, determine_format, request, serializer) - - # typical browser (firefox, chrome) - request.META = {'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'} - self.assertEqual(determine_format(request, serializer), 'application/xml') - - -class TimezoneTestCase(TestCase): - def test_now(self): - without_tz = datetime.datetime(2013, 8, 7, 22, 54, 52) - with_tz = without_tz.replace(tzinfo=Pacific) - - with mock.patch('django.utils.timezone.now', return_value=with_tz): - self.assertEqual(now().isoformat(), '2013-08-08T00:54:52-05:00') - - with mock.patch('django.utils.timezone.now', return_value=without_tz): - self.assertEqual(now().isoformat(), '2013-08-07T22:54:52') diff -Nru django-tastypie-0.13.3/tests/core/tests/validation.py django-tastypie-0.14.3/tests/core/tests/validation.py --- django-tastypie-0.13.3/tests/core/tests/validation.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/tests/validation.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -from django.core.exceptions import ImproperlyConfigured -from django import forms -from django.test import TestCase -from tastypie.bundle import Bundle -from tastypie.validation import Validation, FormValidation, CleanedDataFormValidation - - -class NoteForm(forms.Form): - title = forms.CharField(max_length=100) - slug = forms.CharField(max_length=50) - content = forms.CharField(required=False, widget=forms.Textarea) - is_active = forms.BooleanField() - - def clean_title(self): - return self.cleaned_data.get('title', '').upper() - - # Define a custom clean to make sure non-field errors are making it - # through. - def clean(self): - if not self.cleaned_data.get('content', ''): - raise forms.ValidationError('Having no content makes for a very boring note.') - - return self.cleaned_data - - -class ValidationTestCase(TestCase): - def test_init_no_args(self): - try: - Validation() - except Exception: - self.fail("Initialization failed when it should have succeeded.") - - def test_init_form_class_provided(self): - try: - Validation(form_class='foo') - except Exception: - self.fail("Initialization failed when it should have succeeded again.") - - def test_is_valid(self): - valid = Validation() - bundle = Bundle() - self.assertEqual(valid.is_valid(bundle), {}) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': 'bar', - 'content': '', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), {}) - - -class FormValidationTestCase(TestCase): - def test_init_no_args(self): - self.assertRaises(ImproperlyConfigured, FormValidation) - - def test_init_form_class_provided(self): - try: - FormValidation(form_class=NoteForm) - except Exception: - self.fail("Initialization failed when it should have succeeded.") - - def test_is_valid(self): - valid = FormValidation(form_class=NoteForm) - bundle = Bundle() - self.assertEqual(valid.is_valid(bundle), { - 'is_active': [u'This field is required.'], - 'slug': [u'This field is required.'], - '__all__': [u'Having no content makes for a very boring note.'], - 'title': [u'This field is required.'], - }) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': '123456789012345678901234567890123456789012345678901234567890', - 'content': '', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), { - 'slug': [u'Ensure this value has at most 50 characters (it has 60).'], - '__all__': [u'Having no content makes for a very boring note.'], - }) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': 'bar', - 'content': '', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), { - '__all__': [u'Having no content makes for a very boring note.'], - }) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': 'bar', - 'content': 'This! Is! CONTENT!', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), {}) - # NOTE: Bundle data is left untouched! - self.assertEqual(bundle.data['title'], 'Foo.') - - -class CleanedDataFormValidationTestCase(TestCase): - def test_init_no_args(self): - self.assertRaises(ImproperlyConfigured, CleanedDataFormValidation) - - def test_init_form_class_provided(self): - try: - CleanedDataFormValidation(form_class=NoteForm) - except Exception: - self.fail("Initialization failed when it should have succeeded.") - - def test_is_valid(self): - valid = CleanedDataFormValidation(form_class=NoteForm) - bundle = Bundle() - self.assertEqual(valid.is_valid(bundle), { - 'is_active': [u'This field is required.'], - 'slug': [u'This field is required.'], - '__all__': [u'Having no content makes for a very boring note.'], - 'title': [u'This field is required.'], - }) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': '123456789012345678901234567890123456789012345678901234567890', - 'content': '', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), { - 'slug': [u'Ensure this value has at most 50 characters (it has 60).'], - '__all__': [u'Having no content makes for a very boring note.'], - }) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': 'bar', - 'content': '', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), { - '__all__': [u'Having no content makes for a very boring note.'], - }) - - bundle = Bundle(data={ - 'title': 'Foo.', - 'slug': 'bar', - 'content': 'This! Is! CONTENT!', - 'is_active': True, - }) - self.assertEqual(valid.is_valid(bundle), {}) - # NOTE: Bundle data is modified! - self.assertEqual(bundle.data['title'], u'FOO.') diff -Nru django-tastypie-0.13.3/tests/core/utils.py django-tastypie-0.14.3/tests/core/utils.py --- django-tastypie-0.13.3/tests/core/utils.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/core/utils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -import datetime -import logging -import six - -from tastypie import fields - - -class SimpleHandler(logging.Handler): - logged = [] - - def emit(self, record): - SimpleHandler.logged.append(record) - - -def adjust_schema(schema_dict): - for field, field_info in schema_dict['fields'].items(): - if isinstance(field_info['default'], six.string_types) and field_info['type'] in ('datetime', 'date',): - field_info['default'] = 'The current date.' - if isinstance(field_info['default'], (datetime.datetime, datetime.date)): - field_info['default'] = 'The current date.' - if isinstance(field_info['default'], fields.NOT_PROVIDED): - field_info['default'] = 'No default provided.' - return schema_dict diff -Nru django-tastypie-0.13.3/tests/customuser/fixtures/customuser.json django-tastypie-0.14.3/tests/customuser/fixtures/customuser.json --- django-tastypie-0.13.3/tests/customuser/fixtures/customuser.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/customuser/fixtures/customuser.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -[ - { - "pk": "1", - "model": "customuser.customuser", - "fields": { - "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161", - "last_login": "2006-12-17 07:03:31", - "email": "staffmember@example.com", - "is_active": true, - "is_admin": false, - "date_of_birth": "1976-11-08" - } - } -] diff -Nru django-tastypie-0.13.3/tests/customuser/models.py django-tastypie-0.14.3/tests/customuser/models.py --- django-tastypie-0.13.3/tests/customuser/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/customuser/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -from django.contrib.auth.models import AbstractBaseUser -from django.db import models - - -class CustomUser(AbstractBaseUser): - email = models.EmailField(verbose_name='email address', max_length=255, unique=True) - is_active = models.BooleanField(default=True) - is_admin = models.BooleanField(default=False) - date_of_birth = models.DateField() - - USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['date_of_birth'] - - def get_full_name(self): - return self.email - - def get_short_name(self): - return self.email - - def __unicode__(self): - return self.email - - # Maybe required? - def get_group_permissions(self, obj=None): - return set() - - def get_all_permissions(self, obj=None): - return set() - - def has_perm(self, perm, obj=None): - return True - - def has_perms(self, perm_list, obj=None): - return True - - def has_module_perms(self, app_label): - return True - - # Admin required fields - @property - def is_staff(self): - return self.is_admin diff -Nru django-tastypie-0.13.3/tests/customuser/tests/custom_user.py django-tastypie-0.14.3/tests/customuser/tests/custom_user.py --- django-tastypie-0.13.3/tests/customuser/tests/custom_user.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/customuser/tests/custom_user.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -from django.http import HttpRequest -from django.test import TestCase - -from tastypie.authentication import ApiKeyAuthentication -from tastypie.http import HttpUnauthorized -from tastypie.models import ApiKey, create_api_key - -from customuser.models import CustomUser - - -class CustomUserTestCase(TestCase): - fixtures = ['customuser.json'] - - def setUp(self): - super(CustomUserTestCase, self).setUp() - ApiKey.objects.all().delete() - - def test_is_authenticated_get_params(self): - auth = ApiKeyAuthentication() - request = HttpRequest() - - # Simulate sending the signal. - john_doe = CustomUser.objects.get(pk=1) - create_api_key(CustomUser, instance=john_doe, created=True) - - # No username/api_key details should fail. - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong username (email) details. - request.GET['username'] = 'foo@bar.com' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # No api_key. - request.GET['username'] = john_doe.email - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong user/api_key. - request.GET['username'] = john_doe.email - request.GET['api_key'] = 'foo' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Correct user/api_key. - ApiKey.objects.all().delete() - create_api_key(CustomUser, instance=john_doe, created=True) - request.GET['username'] = john_doe.email - request.GET['api_key'] = john_doe.api_key.key - self.assertEqual(auth.is_authenticated(request), True) - self.assertEqual(auth.get_identifier(request), john_doe.email) diff -Nru django-tastypie-0.13.3/tests/customuser/tests/__init__.py django-tastypie-0.14.3/tests/customuser/tests/__init__.py --- django-tastypie-0.13.3/tests/customuser/tests/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/customuser/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -from customuser.tests.custom_user import * # flake8: noqa diff -Nru django-tastypie-0.13.3/tests/gis/api/resources.py django-tastypie-0.14.3/tests/gis/api/resources.py --- django-tastypie-0.13.3/tests/gis/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -from django.contrib.auth.models import User -from tastypie import fields -from tastypie.resources import ALL -from tastypie.contrib.gis.resources import ModelResource -from tastypie.authorization import Authorization -from gis.models import GeoNote - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - authorization = Authorization() - - -class GeoNoteResource(ModelResource): - user = fields.ForeignKey(UserResource, 'user') - - class Meta: - resource_name = 'geonotes' - queryset = GeoNote.objects.all() - authorization = Authorization() - filtering = { - 'points': ALL, - 'lines': ALL, - 'polys': ALL, - } diff -Nru django-tastypie-0.13.3/tests/gis/api/urls.py django-tastypie-0.14.3/tests/gis/api/urls.py --- django-tastypie-0.13.3/tests/gis/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -from tastypie.api import Api -from gis.api.resources import GeoNoteResource, UserResource - - -api = Api(api_name='v1') -api.register(GeoNoteResource()) -api.register(UserResource()) - -urlpatterns = api.urls diff -Nru django-tastypie-0.13.3/tests/gis/fixtures/test_data.json django-tastypie-0.14.3/tests/gis/fixtures/test_data.json --- django-tastypie-0.13.3/tests/gis/fixtures/test_data.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/fixtures/test_data.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -[ - { - "fields": { - "username": "johndoe", - "email": "john@doe.com", - "password": "this_is_not_a_valid_password_string" - }, - "model": "auth.user", - "pk": 1 - }, - { - "fields": { - "content": "Wooo two points inside Golden Gate park", - "created": "2010-03-30 20:05:00", - "is_active": true, - "lines": null, - "points": "MULTIPOINT (-122.4752327727999983 37.7686172428039981, -122.4704155326000006 37.7673818328299973)", - "polys": null, - "slug": "points-inside-golden-gate-park-note", - "title": "Points inside Golden Gate Park note", - "updated": "2012-03-07 21:47:37", - "user": 1 - }, - "model": "gis.geonote", - "pk": 1 - }, - { - "fields": { - "content": "This is a note about Golden Gate Park. It contains Golden Gate Park's polygon", - "created": "2010-03-31 20:05:00", - "is_active": true, - "lines": null, - "points": null, - "polys": "MULTIPOLYGON (((-122.5110670852699997 37.7712760364340028, -122.5100371170100004 37.7663909693299971, -122.5100371170100004 37.7638126093670010, -122.4568220901600029 37.7658481641770010, -122.4529597091700026 37.7664588196950035, -122.4548479843199971 37.7739898231570024, -122.4753615188599980 37.7730400091340002, -122.5110670852699997 37.7712760364340028)))", - "slug": "another-post", - "title": "Golden Gate Park", - "updated": "2012-03-07 21:48:48", - "user": 1 - }, - "model": "gis.geonote", - "pk": 2 - }, - { - "fields": { - "content": "A path inside Golden Gate Park! Huzzah!", - "created": "2012-03-07 21:51:52", - "is_active": true, - "lines": "MULTILINESTRING ((-122.5045439385699950 37.7670016534969974, -122.4999949120799982 37.7682229404420013, -122.4959608697199940 37.7691728163430014, -122.4950167321500061 37.7692406641549994, -122.4916693353000028 37.7709368392189972, -122.4849745415900060 37.7707333002660022))", - "points": null, - "polys": null, - "slug": "line-inside-golden-gate-park", - "title": "Line inside Golden Gate Park", - "updated": "2012-03-07 21:52:21", - "user": 1 - }, - "model": "gis.geonote", - "pk": 3 - } -] diff -Nru django-tastypie-0.13.3/tests/gis/models.py django-tastypie-0.14.3/tests/gis/models.py --- django-tastypie-0.13.3/tests/gis/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -from django.contrib.auth.models import User -from django.contrib.gis.db import models - -from tastypie.utils import now - - -class GeoNote(models.Model): - user = models.ForeignKey(User, related_name='notes') - title = models.CharField(max_length=255) - slug = models.SlugField() - content = models.TextField() - is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=now) - updated = models.DateTimeField(default=now) - - points = models.MultiPointField(null=True, blank=True) - lines = models.MultiLineStringField(null=True, blank=True) - polys = models.MultiPolygonField(null=True, blank=True) - - objects = models.GeoManager() - - def __unicode__(self): - return self.title - - def save(self, *args, **kwargs): - self.updated = now() - return super(GeoNote, self).save(*args, **kwargs) - - -class AnnotatedGeoNote(models.Model): - note = models.OneToOneField(GeoNote, related_name='annotated', null=True) - annotations = models.TextField() - - def __unicode__(self): - return u"Annotated %s" % self.note.title diff -Nru django-tastypie-0.13.3/tests/gis/tests/http.py django-tastypie-0.14.3/tests/gis/tests/http.py --- django-tastypie-0.13.3/tests/gis/tests/http.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/tests/http.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,231 +0,0 @@ -import json - -try: - from http.client import HTTPConnection -except ImportError: - from httplib import HTTPConnection - -try: - from urllib.parse import quote -except ImportError: - from urllib import quote - -from testcases import TestServerTestCase - -from .utils import skipIfSpatialite - -golden_gate_park_query = quote("""{"type": "MultiPolygon", "coordinates": [[[[-122.511067, 37.771276], [-122.510037, 37.766391], [-122.510037, 37.763813], [-122.456822, 37.765848], [-122.452960, 37.766459], [-122.454848, 37.773990], [-122.475362, 37.773040], [-122.511067, 37.771276]]]]}""") - - -class HTTPTestCase(TestServerTestCase): - def setUp(self): - self.start_test_server(address='localhost', port=8001) - - def tearDown(self): - self.stop_test_server() - - def get_connection(self): - return HTTPConnection('localhost', 8001) - - def test_get_apis_json(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - data = json.loads(response.read().decode('utf-8')) - self.assertEqual(response.status, 200) - self.assertEqual(data, {"geonotes": {"list_endpoint": "/api/v1/geonotes/", "schema": "/api/v1/geonotes/schema/"}, "users": {"list_endpoint": "/api/v1/users/", "schema": "/api/v1/users/schema/"}}) - - def test_get_apis_xml(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/', headers={'Accept': 'application/xml'}) - response = connection.getresponse() - connection.close() - data = response.read().decode('utf-8') - self.assertEqual(response.status, 200) - self.assertEqual(data, '\n/api/v1/geonotes//api/v1/geonotes/schema//api/v1/users//api/v1/users/schema/') - - def test_get_list(self): - connection = self.get_connection() - connection.request('GET', '/api/v1/geonotes/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - data = json.loads(response.read().decode('utf-8')) - self.assertEqual(response.status, 200) - self.assertEqual(len(data['objects']), 3) - - # Because floating point. - self.assertEqual(data['objects'][0]['content'], "Wooo two points inside Golden Gate park") - self.assertEqual(data['objects'][0]['points']['type'], 'MultiPoint') - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][0][0], -122.475233, places=5) - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][0][1], 37.768616, places=5) - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][1][0], -122.470416, places=5) - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][1][1], 37.767381, places=5) - - self.assertEqual(data['objects'][1]['content'], "This is a note about Golden Gate Park. It contains Golden Gate Park\'s polygon") - self.assertEqual(data['objects'][1]['polys']['type'], 'MultiPolygon') - self.assertEqual(len(data['objects'][1]['polys']['coordinates']), 1) - self.assertEqual(len(data['objects'][1]['polys']['coordinates'][0]), 1) - self.assertEqual(len(data['objects'][1]['polys']['coordinates'][0][0]), 8) - - self.assertEqual(data['objects'][2]['content'], "A path inside Golden Gate Park! Huzzah!") - self.assertEqual(data['objects'][2]['lines']['type'], 'MultiLineString') - self.assertAlmostEqual(data['objects'][2]['lines']['coordinates'][0][0][0], -122.504544, places=5) - self.assertAlmostEqual(data['objects'][2]['lines']['coordinates'][0][0][1], 37.767002, places=5) - self.assertAlmostEqual(data['objects'][2]['lines']['coordinates'][0][1][0], -122.499995, places=5) - self.assertAlmostEqual(data['objects'][2]['lines']['coordinates'][0][1][1], 37.768223, places=5) - - def test_post_object(self): - connection = self.get_connection() - post_data = '{"content": "A new post.", "is_active": true, "title": "New Title", "slug": "new-title", "user": "/api/v1/users/1/"}' - connection.request('POST', '/api/v1/geonotes/', body=post_data, headers={'Accept': 'application/json', 'Content-type': 'application/json'}) - - response = connection.getresponse() - - self.assertEqual(response.status, 201) - - headers = dict(response.getheaders()) - - location = headers.get('location', headers.get('Location')) - self.assertTrue(location.endswith('/api/v1/geonotes/4/')) - - # make sure posted object exists - connection.request('GET', '/api/v1/geonotes/4/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - - self.assertEqual(response.status, 200) - - data = response.read().decode('utf-8') - obj = json.loads(data) - - self.assertEqual(obj['content'], 'A new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - - def test_post_geojson(self): - connection = self.get_connection() - post_data = """{ - "content": "A new post.", "is_active": true, "title": "New Title2", - "slug": "new-title2", "user": "/api/v1/users/1/", - "polys": { "type": "MultiPolygon", "coordinates": [ [ [ [ -122.511067, 37.771276 ], [ -122.510037, 37.766391 ], [ -122.510037, 37.763813 ], [ -122.456822, 37.765848 ], [ -122.452960, 37.766459 ], [ -122.454848, 37.773990 ], [ -122.475362, 37.773040 ], [ -122.511067, 37.771276 ] ] ] ] } - }""" - connection.request('POST', '/api/v1/geonotes/', body=post_data, headers={'Accept': 'application/json', 'Content-type': 'application/json'}) - response = connection.getresponse() - - self.assertEqual(response.status, 201) - - headers = dict(response.getheaders()) - - location = headers.get('location', headers.get('Location')) - self.assertTrue(location.endswith('/api/v1/geonotes/4/')) - - # make sure posted object exists - connection.request('GET', '/api/v1/geonotes/4/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - - self.assertEqual(response.status, 200) - - data = response.read().decode('utf-8') - obj = json.loads(data) - - self.assertEqual(obj['content'], 'A new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - self.assertEqual(obj['polys'], {u'type': u'MultiPolygon', u'coordinates': [[[[-122.511067, 37.771276], [-122.510037, 37.766390999999999], [-122.510037, 37.763812999999999], [-122.456822, 37.765847999999998], [-122.45296, 37.766458999999998], [-122.454848, 37.773989999999998], [-122.475362, 37.773040000000002], [-122.511067, 37.771276]]]]}) - - def test_post_xml(self): - connection = self.get_connection() - post_data = """2010-03-30T20:05:00TruePoints inside Golden Gate Park note 2points-inside-golden-gate-park-note-2A new post.MultiPoint-122.47523337.768617-122.47041637.767382/api/v1/users/1/""" - connection.request('POST', '/api/v1/geonotes/', body=post_data, headers={'Accept': 'application/xml', 'Content-type': 'application/xml'}) - response = connection.getresponse() - - self.assertEqual(response.status, 201) - - headers = dict(response.getheaders()) - - location = headers.get('location', headers.get('Location')) - self.assertTrue(location.endswith('/api/v1/geonotes/4/')) - - # make sure posted object exists - connection.request('GET', '/api/v1/geonotes/4/', headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - - self.assertEqual(response.status, 200) - - data = response.read().decode('utf-8') - obj = json.loads(data) - - self.assertEqual(obj['content'], 'A new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - # Weeeee! GeoJSON returned! - self.assertEqual(obj['points'], {"coordinates": [[-122.475233, 37.768616999999999], [-122.470416, 37.767381999999998]], "type": "MultiPoint"}) - - # Or we can ask for XML - connection.request('GET', '/api/v1/geonotes/4/', headers={'Accept': 'application/xml'}) - response = connection.getresponse() - connection.close() - - self.assertEqual(response.status, 200) - data = response.read().decode('utf-8') - - self.assertIn('-122.47523337.768617-122.47041637.767382MultiPoint', data) - - def test_filter_within_on_points(self): - - # Get points - connection = self.get_connection() - connection.request('GET', '/api/v1/geonotes/?points__within=%s' % golden_gate_park_query, headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - data = json.loads(response.read().decode('utf-8')) - # We get back the points inside Golden Gate park! - self.assertEqual(data['objects'][0]['content'], "Wooo two points inside Golden Gate park") - self.assertEqual(data['objects'][0]['points']['type'], 'MultiPoint') - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][0][0], -122.475233, places=5) - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][0][1], 37.768616, places=5) - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][1][0], -122.470416, places=5) - self.assertAlmostEqual(data['objects'][0]['points']['coordinates'][1][1], 37.767381, places=5) - - @skipIfSpatialite - def test_filter_within_on_lines(self): - - # Get lines - connection = self.get_connection() - connection.request('GET', '/api/v1/geonotes/?lines__within=%s' % golden_gate_park_query, headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - data = json.loads(response.read().decode('utf-8')) - # We get back the line inside Golden Gate park! - self.assertEqual(data['objects'][0]['content'], "A path inside Golden Gate Park! Huzzah!") - self.assertEqual(data['objects'][0]['lines']['type'], 'MultiLineString') - self.assertAlmostEqual(data['objects'][0]['lines']['coordinates'][0][0][0], -122.504544, places=5) - self.assertAlmostEqual(data['objects'][0]['lines']['coordinates'][0][0][1], 37.767002, places=5) - self.assertAlmostEqual(data['objects'][0]['lines']['coordinates'][0][1][0], -122.499995, places=5) - self.assertAlmostEqual(data['objects'][0]['lines']['coordinates'][0][1][1], 37.768223, places=5) - - @skipIfSpatialite - def test_filter_contains(self): - points_inside_golden_gate_park = """{"coordinates": [[-122.475233, 37.768616999999999], [-122.470416, 37.767381999999998]], "type": "MultiPoint"}""" - - # Get polys that contain the points - connection = self.get_connection() - connection.request('GET', '/api/v1/geonotes/?polys__contains=%s' % quote(points_inside_golden_gate_park), headers={'Accept': 'application/json'}) - response = connection.getresponse() - connection.close() - self.assertEqual(response.status, 200) - - data = json.loads(response.read().decode('utf-8')) - # We get back the golden gate park polygon! - self.assertEqual(data['objects'][0]['content'], "This is a note about Golden Gate Park. It contains Golden Gate Park\'s polygon") - self.assertEqual(data['objects'][0]['polys']['type'], 'MultiPolygon') - self.assertEqual(len(data['objects'][0]['polys']['coordinates']), 1) - self.assertEqual(len(data['objects'][0]['polys']['coordinates'][0]), 1) - self.assertEqual(len(data['objects'][0]['polys']['coordinates'][0][0]), 8) diff -Nru django-tastypie-0.13.3/tests/gis/tests/__init__.py django-tastypie-0.14.3/tests/gis/tests/__init__.py --- django-tastypie-0.13.3/tests/gis/tests/__init__.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -from gis.tests.http import * # flake8: noqa -from gis.tests.views import * # flake8: noqa diff -Nru django-tastypie-0.13.3/tests/gis/tests/utils.py django-tastypie-0.14.3/tests/gis/tests/utils.py --- django-tastypie-0.13.3/tests/gis/tests/utils.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/tests/utils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -from unittest import skipIf -from django.conf import settings - -skipIfSpatialite = skipIf('spatialite' in settings.DATABASES['default']['ENGINE'], "Spatialite not supported") diff -Nru django-tastypie-0.13.3/tests/gis/tests/views.py django-tastypie-0.14.3/tests/gis/tests/views.py --- django-tastypie-0.13.3/tests/gis/tests/views.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/tests/views.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,104 +0,0 @@ -import json - -from django.http import HttpRequest - -from testcases import TestCaseWithFixture - - -class ViewsTestCase(TestCaseWithFixture): - def test_gets(self): - resp = self.client.get('/api/v1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['geonotes'], {'list_endpoint': '/api/v1/geonotes/', 'schema': '/api/v1/geonotes/schema/'}) - - resp = self.client.get('/api/v1/geonotes/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['meta']['limit'], 20) - self.assertEqual(len(deserialized['objects']), 3) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'Points inside Golden Gate Park note', u'Golden Gate Park', u'Line inside Golden Gate Park']) - - resp = self.client.get('/api/v1/geonotes/1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 12) - self.assertEqual(deserialized['title'], u'Points inside Golden Gate Park note') - - resp = self.client.get('/api/v1/geonotes/set/2;1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 1) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'Golden Gate Park', u'Points inside Golden Gate Park note']) - - def test_posts(self): - request = HttpRequest() - post_data = '{"content": "A new post.", "is_active": true, "title": "New Title", "slug": "new-title", "user": "/api/v1/users/1/"}' - request._body = request._raw_post_data = post_data - - resp = self.client.post('/api/v1/geonotes/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - self.assertTrue(resp['location'].endswith('/api/v1/geonotes/4/')) - - # make sure posted object exists - resp = self.client.get('/api/v1/geonotes/4/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['content'], 'A new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - - def test_puts(self): - request = HttpRequest() - post_data = '{"content": "Another new post.", "is_active": true, "title": "Another New Title", "slug": "new-title", "user": "/api/v1/users/1/", "lines": null, "points": null, "polys": null}' - request._body = request._raw_post_data = post_data - - resp = self.client.put('/api/v1/geonotes/1/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 204) - - # make sure posted object exists - resp = self.client.get('/api/v1/geonotes/1/', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - obj = json.loads(resp.content.decode('utf-8')) - self.assertEqual(obj['content'], 'Another new post.') - self.assertEqual(obj['is_active'], True) - self.assertEqual(obj['user'], '/api/v1/users/1/') - - def test_api_field_error(self): - # When a field error is encountered, we should be presenting the message - # back to the user. - request = HttpRequest() - post_data = '{"content": "More internet memes.", "is_active": true, "title": "IT\'S OVER 9000!", "slug": "its-over", "user": "/api/v1/users/9001/"}' - request._body = request._raw_post_data = post_data - - resp = self.client.post('/api/v1/geonotes/', data=post_data, content_type='application/json') - self.assertEqual(resp.status_code, 400) - self.assertEqual(resp.content.decode('utf-8'), '{"error": "Could not find the provided users object via resource URI \'/api/v1/users/9001/\'."}') - - def test_options(self): - resp = self.client.options('/api/v1/geonotes/') - self.assertEqual(resp.status_code, 200) - allows = 'GET,POST,PUT,DELETE,PATCH' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - resp = self.client.options('/api/v1/geonotes/1/') - self.assertEqual(resp.status_code, 200) - allows = 'GET,POST,PUT,DELETE,PATCH' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - resp = self.client.options('/api/v1/geonotes/schema/') - self.assertEqual(resp.status_code, 200) - allows = 'GET' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) - - resp = self.client.options('/api/v1/geonotes/set/2;1/') - self.assertEqual(resp.status_code, 200) - allows = 'GET' - self.assertEqual(resp['Allow'], allows) - self.assertEqual(resp.content.decode('utf-8'), allows) diff -Nru django-tastypie-0.13.3/tests/gis/urls.py django-tastypie-0.14.3/tests/gis/urls.py --- django-tastypie-0.13.3/tests/gis/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/gis/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -from django.conf.urls import include, url - - -urlpatterns = [ - url(r'^api/', include('gis.api.urls')), -] diff -Nru django-tastypie-0.13.3/tests/manage_alphanumeric.py django-tastypie-0.14.3/tests/manage_alphanumeric.py --- django-tastypie-0.13.3/tests/manage_alphanumeric.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_alphanumeric.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_alphanumeric") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_authorization.py django-tastypie-0.14.3/tests/manage_authorization.py --- django-tastypie-0.13.3/tests/manage_authorization.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_authorization.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_authorization") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_basic.py django-tastypie-0.14.3/tests/manage_basic.py --- django-tastypie-0.13.3/tests/manage_basic.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_basic.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_basic") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_content_gfk.py django-tastypie-0.14.3/tests/manage_content_gfk.py --- django-tastypie-0.13.3/tests/manage_content_gfk.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_content_gfk.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_content_gfk") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_core.py django-tastypie-0.14.3/tests/manage_core.py --- django-tastypie-0.13.3/tests/manage_core.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_core.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_core") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_customuser.py django-tastypie-0.14.3/tests/manage_customuser.py --- django-tastypie-0.13.3/tests/manage_customuser.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_customuser.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_customuser") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_gis.py django-tastypie-0.14.3/tests/manage_gis.py --- django-tastypie-0.13.3/tests/manage_gis.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_gis.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_gis") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_gis_spatialite.py django-tastypie-0.14.3/tests/manage_gis_spatialite.py --- django-tastypie-0.13.3/tests/manage_gis_spatialite.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_gis_spatialite.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_gis_spatialite") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_namespaced.py django-tastypie-0.14.3/tests/manage_namespaced.py --- django-tastypie-0.13.3/tests/manage_namespaced.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_namespaced.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_namespaced") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_profilingtests.py django-tastypie-0.14.3/tests/manage_profilingtests.py --- django-tastypie-0.13.3/tests/manage_profilingtests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_profilingtests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_profilingtests") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_related.py django-tastypie-0.14.3/tests/manage_related.py --- django-tastypie-0.13.3/tests/manage_related.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_related.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_related") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_slashless.py django-tastypie-0.14.3/tests/manage_slashless.py --- django-tastypie-0.13.3/tests/manage_slashless.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_slashless.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_slashless") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/manage_validation.py django-tastypie-0.14.3/tests/manage_validation.py --- django-tastypie-0.13.3/tests/manage_validation.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/manage_validation.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_validation") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff -Nru django-tastypie-0.13.3/tests/namespaced/api/resources.py django-tastypie-0.14.3/tests/namespaced/api/resources.py --- django-tastypie-0.13.3/tests/namespaced/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/namespaced/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -from django.contrib.auth.models import User -from tastypie import fields -from tastypie.resources import NamespacedModelResource -from tastypie.authorization import Authorization -from basic.models import Note - - -class NamespacedUserResource(NamespacedModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - authorization = Authorization() - - -class NamespacedNoteResource(NamespacedModelResource): - user = fields.ForeignKey(NamespacedUserResource, 'user') - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - authorization = Authorization() diff -Nru django-tastypie-0.13.3/tests/namespaced/api/urls.py django-tastypie-0.14.3/tests/namespaced/api/urls.py --- django-tastypie-0.13.3/tests/namespaced/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/namespaced/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -from django.conf.urls import include, url -from tastypie.api import NamespacedApi -from namespaced.api.resources import NamespacedNoteResource, NamespacedUserResource - - -api = NamespacedApi(api_name='v1', urlconf_namespace='special') -api.register(NamespacedNoteResource(), canonical=True) -api.register(NamespacedUserResource(), canonical=True) - -urlpatterns = [ - url(r'^api/', include(api.urls, namespace='special')), -] diff -Nru django-tastypie-0.13.3/tests/namespaced/fixtures/test_data.json django-tastypie-0.14.3/tests/namespaced/fixtures/test_data.json --- django-tastypie-0.13.3/tests/namespaced/fixtures/test_data.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/namespaced/fixtures/test_data.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -[ - { - "fields": { - "username": "johndoe", - "email": "john@doe.com", - "password": "this_is_not_a_valid_password_string" - }, - "model": "auth.user", - "pk": 1 - }, - - { - "fields": { - "user": 1, - "title": "First Post!", - "slug": "first-post", - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "is_active": true, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "basic.note", - "pk": 1 - }, - { - "fields": { - "user": 1, - "title": "Another Post", - "slug": "another-post", - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "is_active": true, - "created": "2010-03-31 20:05:00", - "updated": "2010-03-31 20:05:00" - }, - "model": "basic.note", - "pk": 2 - } -] diff -Nru django-tastypie-0.13.3/tests/namespaced/tests.py django-tastypie-0.14.3/tests/namespaced/tests.py --- django-tastypie-0.13.3/tests/namespaced/tests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/namespaced/tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -from django.core.urlresolvers import reverse, NoReverseMatch -from testcases import TestCaseWithFixture - - -class NamespacedViewsTestCase(TestCaseWithFixture): - urls = 'namespaced.api.urls' - - def test_urls(self): - from namespaced.api.urls import api - patterns = api.urls - self.assertEqual(len(patterns), 3) - self.assertEqual(sorted([pattern.name for pattern in patterns if hasattr(pattern, 'name')]), ['api_v1_top_level']) - self.assertEqual([[pattern.name for pattern in include.url_patterns if hasattr(pattern, 'name')] for include in patterns if hasattr(include, 'reverse_dict')], [['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail'], ['api_dispatch_list', 'api_get_schema', 'api_get_multiple', 'api_dispatch_detail']]) - - self.assertRaises(NoReverseMatch, reverse, 'api_v1_top_level') - self.assertRaises(NoReverseMatch, reverse, 'special:api_v1_top_level') - self.assertEquals(reverse('special:api_v1_top_level', kwargs={'api_name': 'v1'}), '/api/v1/') - self.assertEquals(reverse('special:api_dispatch_list', kwargs={'api_name': 'v1', 'resource_name': 'notes'}), '/api/v1/notes/') diff -Nru django-tastypie-0.13.3/tests/profilingtests/models.py django-tastypie-0.14.3/tests/profilingtests/models.py --- django-tastypie-0.13.3/tests/profilingtests/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/profilingtests/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -from django.contrib.auth.models import User -from django.db import models -from tastypie.utils import now, aware_datetime - - -class Note(models.Model): - author = models.ForeignKey(User, blank=True, null=True) - title = models.CharField(max_length=100) - slug = models.SlugField() - content = models.TextField(blank=True) - is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=now) - updated = models.DateTimeField(default=now) - - def __unicode__(self): - return self.title - - def save(self, *args, **kwargs): - self.updated = now() - return super(Note, self).save(*args, **kwargs) - - def what_time_is_it(self): - return aware_datetime(2010, 4, 1, 0, 48) - - def get_absolute_url(self): - return '/some/fake/path/%s/' % self.pk - - @property - def my_property(self): - return 'my_property' diff -Nru django-tastypie-0.13.3/tests/profilingtests/resources.py django-tastypie-0.14.3/tests/profilingtests/resources.py --- django-tastypie-0.13.3/tests/profilingtests/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/profilingtests/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -from django.contrib.auth.models import User - -from tastypie import fields -from tastypie.authorization import Authorization -from tastypie.resources import ModelResource, ALL - -from .models import Note - - -class UserResource(ModelResource): - class Meta: - queryset = User.objects.all() - authorization = Authorization() - - -class NoteResource(ModelResource): - author = fields.ToOneField(UserResource, 'author', full=True) - - class Meta: - resource_name = 'notes' - authorization = Authorization() - filtering = { - 'content': ['startswith', 'exact'], - 'title': ALL, - 'slug': ['exact'], - } - ordering = ['title', 'slug', 'resource_uri'] - queryset = Note.objects.all() diff -Nru django-tastypie-0.13.3/tests/profilingtests/tests.py django-tastypie-0.14.3/tests/profilingtests/tests.py --- django-tastypie-0.13.3/tests/profilingtests/tests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/profilingtests/tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -import cProfile -import pstats - -from django.contrib.auth.models import User -from django.test import TestCase - -from core.tests.mocks import MockRequest - -from .models import Note -from .resources import NoteResource - - -class ProfilingTestCase(TestCase): - def setUp(self): - self.pr = cProfile.Profile() - self.pr.enable() - - def tearDown(self): - p = pstats.Stats(self.pr) - # p.strip_dirs() - p.sort_stats('tottime') - p.print_stats(75) - - -class ResourceProfilingTestCase(ProfilingTestCase): - def setUp(self): - self.resource = NoteResource() - self.request = MockRequest() - self.request.path = '/api/v1/notes/' - self.request.GET = {'limit': '100'} - - user = User.objects.create_user('foo', 'pass') - - for i in xrange(0, 200): - Note.objects.create(author=user, title='Note #%s' % i, - slug='note-%s' % i) - - super(ResourceProfilingTestCase, self).setUp() - - def test_get_list(self): - get_list = self.resource.get_list - request = self.request - - for i in xrange(0, 50): - get_list(request) diff -Nru django-tastypie-0.13.3/tests/profilingtests/urls.py django-tastypie-0.14.3/tests/profilingtests/urls.py --- django-tastypie-0.13.3/tests/profilingtests/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/profilingtests/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -from django.conf.urls import include, url - -from tastypie.api import Api - -from .resources import NoteResource, UserResource - - -api = Api() -api.register(NoteResource()) -api.register(UserResource()) - -urlpatterns = [ - url(r'^api/', include(api.urls)), -] diff -Nru django-tastypie-0.13.3/tests/related_resource/api/resources.py django-tastypie-0.14.3/tests/related_resource/api/resources.py --- django-tastypie-0.13.3/tests/related_resource/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/related_resource/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,280 +0,0 @@ -from django.contrib.auth.models import User - -from tastypie import fields -from tastypie.resources import ModelResource -from tastypie.authorization import Authorization - -from core.models import Note, MediaBit - -from related_resource.models import Bone, Category, Contact, ContactGroup,\ - ExtraData, Person, Company, Product, Address, Dog, DogHouse, Forum,\ - Job, Label, Order, OrderItem, Payment, Post, Tag, Taggable, TaggableTag - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - allowed_methods = ['get'] - authorization = Authorization() - - -class UpdatableUserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - allowed_methods = ['get', 'put'] - authorization = Authorization() - - -class NoteResource(ModelResource): - author = fields.ForeignKey(UserResource, 'author') - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - authorization = Authorization() - - -class NoteWithUpdatableUserResource(ModelResource): - author = fields.ForeignKey(UpdatableUserResource, 'author') - - class Meta: - resource_name = 'noteswithupdatableuser' - queryset = Note.objects.all() - authorization = Authorization() - - -class CategoryResource(ModelResource): - parent = fields.ToOneField('self', 'parent', null=True) - - class Meta: - resource_name = 'category' - queryset = Category.objects.all() - authorization = Authorization() - - -class TagResource(ModelResource): - taggabletags = fields.ToManyField( - 'related_resource.api.resources.TaggableTagResource', 'taggabletags', - null=True) - - extradata = fields.ToOneField( - 'related_resource.api.resources.ExtraDataResource', 'extradata', - null=True, blank=True, full=True) - - class Meta: - resource_name = 'tag' - queryset = Tag.objects.all() - authorization = Authorization() - - -class TaggableResource(ModelResource): - taggabletags = fields.ToManyField( - 'related_resource.api.resources.TaggableTagResource', 'taggabletags', - null=True) - - class Meta: - resource_name = 'taggable' - queryset = Taggable.objects.all() - authorization = Authorization() - - -class TaggableTagResource(ModelResource): - tag = fields.ToOneField( - 'related_resource.api.resources.TagResource', 'tag', - null=True) - taggable = fields.ToOneField( - 'related_resource.api.resources.TaggableResource', 'taggable', - null=True) - - class Meta: - resource_name = 'taggabletag' - queryset = TaggableTag.objects.all() - authorization = Authorization() - - -class ExtraDataResource(ModelResource): - tag = fields.ToOneField( - 'related_resource.api.resources.TagResource', 'tag', - null=True) - - class Meta: - resource_name = 'extradata' - queryset = ExtraData.objects.all() - authorization = Authorization() - - -class FreshNoteResource(ModelResource): - media_bits = fields.ToManyField( - 'related_resource.api.resources.FreshMediaBitResource', 'media_bits', - related_name='note') - - class Meta: - queryset = Note.objects.all() - resource_name = 'freshnote' - authorization = Authorization() - - -class FreshMediaBitResource(ModelResource): - note = fields.ToOneField(FreshNoteResource, 'note') - - class Meta: - queryset = MediaBit.objects.all() - resource_name = 'freshmediabit' - authorization = Authorization() - - -class AddressResource(ModelResource): - class Meta: - queryset = Address.objects.all() - resource_name = 'address' - authorization = Authorization() - - -class ProductResource(ModelResource): - producer = fields.ToOneField( - 'related_resource.api.resources.CompanyResource', 'producer') - - class Meta: - queryset = Product.objects.all() - resource_name = 'product' - authorization = Authorization() - - -class CompanyResource(ModelResource): - address = fields.ToOneField(AddressResource, 'address', null=True, - full=True) - products = fields.ToManyField(ProductResource, 'products', full=True, - related_name='producer', null=True) - - class Meta: - queryset = Company.objects.all() - resource_name = 'company' - authorization = Authorization() - - -class PersonResource(ModelResource): - company = fields.ToOneField(CompanyResource, 'company', null=True, - full=True) - dogs = fields.ToManyField('related_resource.api.resources.DogResource', - 'dogs', full=True, related_name='owner', null=True) - - class Meta: - queryset = Person.objects.all() - resource_name = 'person' - authorization = Authorization() - - -class DogHouseResource(ModelResource): - class Meta: - queryset = DogHouse.objects.all() - resource_name = 'doghouse' - authorization = Authorization() - - -class BoneResource(ModelResource): - dog = fields.ToOneField('related_resource.api.resources.DogResource', - 'dog', null=True) - - class Meta: - queryset = Bone.objects.all() - resource_name = 'bone' - authorization = Authorization() - - -class DogResource(ModelResource): - owner = fields.ToOneField(PersonResource, 'owner') - house = fields.ToOneField(DogHouseResource, 'house', full=True, null=True) - bones = fields.ToManyField(BoneResource, 'bones', full=True, null=True, - related_name='dog') - - class Meta: - queryset = Dog.objects.all() - resource_name = 'dog' - authorization = Authorization() - - -class LabelResource(ModelResource): - class Meta: - resource_name = 'label' - queryset = Label.objects.all() - authorization = Authorization() - - -class PostResource(ModelResource): - label = fields.ToManyField(LabelResource, 'label', null=True) - - class Meta: - queryset = Post.objects.all() - resource_name = 'post' - authorization = Authorization() - - -class PaymentResource(ModelResource): - job = fields.ToOneField('related_resource.api.resources.JobResource', - 'job') - - class Meta: - queryset = Payment.objects.all() - resource_name = 'payment' - authorization = Authorization() - allowed_methods = ('get', 'put', 'post') - - -class JobResource(ModelResource): - payment = fields.ToOneField(PaymentResource, 'payment', related_name='job') - - class Meta: - queryset = Job.objects.all() - resource_name = 'job' - authorization = Authorization() - allowed_methods = ('get', 'put', 'post') - - -class ForumResource(ModelResource): - moderators = fields.ManyToManyField(UserResource, 'moderators', full=True) - members = fields.ManyToManyField(UserResource, 'members', full=True) - - class Meta: - resource_name = 'forum' - queryset = Forum.objects.prefetch_related('moderators', 'members') - authorization = Authorization() - always_return_data = True - - -class OrderItemResource(ModelResource): - order = fields.ForeignKey("related_resource.api.resources.OrderResource", "order") - - class Meta: - queryset = OrderItem.objects.all() - resource_name = 'orderitem' - authorization = Authorization() - - -class OrderResource(ModelResource): - items = fields.ToManyField("related_resource.api.resources.OrderItemResource", "items", - related_name="order", full=True) - - class Meta: - queryset = Order.objects.all() - resource_name = 'order' - authorization = Authorization() - - -class ContactGroupResource(ModelResource): - members = fields.ToManyField('related_resource.api.resources.ContactResource', 'members', related_name='groups', null=True, blank=True) - - class Meta: - queryset = ContactGroup.objects.prefetch_related('members') - resource_name = 'contactgroup' - authorization = Authorization() - - -class ContactResource(ModelResource): - groups = fields.ToManyField(ContactGroupResource, 'groups', related_name='members', null=True, blank=True) - - class Meta: - queryset = Contact.objects.prefetch_related('groups') - resource_name = 'contact' - authorization = Authorization() diff -Nru django-tastypie-0.13.3/tests/related_resource/api/urls.py django-tastypie-0.14.3/tests/related_resource/api/urls.py --- django-tastypie-0.13.3/tests/related_resource/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/related_resource/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -from tastypie.api import Api - -from related_resource.api.resources import NoteResource, UserResource,\ - CategoryResource, TagResource, TaggableTagResource, TaggableResource,\ - ExtraDataResource, FreshNoteResource, FreshMediaBitResource,\ - ForumResource, CompanyResource, ProductResource, AddressResource,\ - PersonResource, DogResource, DogHouseResource, BoneResource,\ - LabelResource, PostResource, OrderResource, OrderItemResource,\ - NoteWithUpdatableUserResource, ContactResource, ContactGroupResource - - -api = Api(api_name='v1') -api.register(NoteResource(), canonical=True) -api.register(NoteWithUpdatableUserResource(), canonical=True) -api.register(UserResource(), canonical=True) -api.register(CategoryResource(), canonical=True) -api.register(TagResource(), canonical=True) -api.register(TaggableResource(), canonical=True) -api.register(TaggableTagResource(), canonical=True) -api.register(ExtraDataResource(), canonical=True) -api.register(FreshNoteResource(), canonical=True) -api.register(FreshMediaBitResource(), canonical=True) -api.register(ForumResource(), canonical=True) -api.register(CompanyResource(), canonical=True) -api.register(ProductResource(), canonical=True) -api.register(AddressResource(), canonical=True) -api.register(PersonResource(), canonical=True) -api.register(DogResource(), canonical=True) -api.register(DogHouseResource(), canonical=True) -api.register(BoneResource(), canonical=True) -api.register(PostResource(), canonical=True) -api.register(LabelResource(), canonical=True) -api.register(OrderResource(), canonical=True) -api.register(OrderItemResource(), canonical=True) -api.register(ContactResource(), canonical=True) -api.register(ContactGroupResource(), canonical=True) - -urlpatterns = api.urls diff -Nru django-tastypie-0.13.3/tests/related_resource/fixtures/test_data.json django-tastypie-0.14.3/tests/related_resource/fixtures/test_data.json --- django-tastypie-0.13.3/tests/related_resource/fixtures/test_data.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/related_resource/fixtures/test_data.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -[ - { - "fields": { - "username": "johndoe", - "email": "john@doe.com", - "password": "this_is_not_a_valid_password_string" - }, - "model": "auth.user", - "pk": 1 - }, - - { - "fields": { - "author": 1, - "title": "First Post!", - "slug": "first-post", - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "is_active": true, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "core.note", - "pk": 1 - }, - { - "fields": { - "author": 1, - "title": "Another Post", - "slug": "another-post", - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "is_active": true, - "created": "2010-03-31 20:05:00", - "updated": "2010-03-31 20:05:00" - }, - "model": "core.note", - "pk": 2 - }, - { - "fields": { - "name": "coffee" - }, - "model": "related_resource.label", - "pk": 1 - } -] diff -Nru django-tastypie-0.13.3/tests/related_resource/models.py django-tastypie-0.14.3/tests/related_resource/models.py --- django-tastypie-0.13.3/tests/related_resource/models.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/related_resource/models.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,172 +0,0 @@ -from django.contrib.auth.models import User -from django.db import models - - -# A self-referrential model to test regressions. -class Category(models.Model): - parent = models.ForeignKey('self', null=True) - name = models.CharField(max_length=32) - - def __unicode__(self): - return u"%s (%s)" % (self.name, self.parent) - - -# A taggable model. Just that. -class Taggable(models.Model): - name = models.CharField(max_length=32) - - -# Explicit intermediary 'through' table -class TaggableTag(models.Model): - tag = models.ForeignKey( - 'Tag', - related_name='taggabletags', - null=True, blank=True, # needed at creation time - ) - taggable = models.ForeignKey( - 'Taggable', - related_name='taggabletags', - null=True, blank=True, # needed at creation time - ) - extra = models.IntegerField(default=0) # extra data about the relationship - - -# Tags to Taggable model through explicit M2M table -class Tag(models.Model): - name = models.CharField(max_length=32) - tagged = models.ManyToManyField( - 'Taggable', - through='TaggableTag', - related_name='tags', - ) - - def __unicode__(self): - return u"%s" % (self.name) - - -# A model that contains additional data for Tag -class ExtraData(models.Model): - name = models.CharField(max_length=32) - tag = models.OneToOneField( - 'Tag', - related_name='extradata', - null=True, blank=True, - ) - - def __unicode__(self): - return u"%s" % (self.name) - - -class Address(models.Model): - line = models.CharField(max_length=32) - - def __unicode__(self): - return u"%s" % (self.line) - - -class Company(models.Model): - name = models.CharField(max_length=32) - address = models.ForeignKey(Address, null=True) - - def __unicode__(self): - return u"%s" % (self.name) - - -class Product(models.Model): - name = models.CharField(max_length=32) - producer = models.ForeignKey(Company, related_name="products") - - def __unicode__(self): - return u"%s" % (self.name) - - -class Person(models.Model): - name = models.CharField(max_length=32) - company = models.ForeignKey(Company, related_name="employees", null=True) - - def __unicode__(self): - return u"%s" % (self.name) - - -class DogHouse(models.Model): - color = models.CharField(max_length=32) - - def __unicode__(self): - return u"%s" % (self.color) - - -class Dog(models.Model): - name = models.CharField(max_length=32) - owner = models.ForeignKey(Person, related_name="dogs") - house = models.ForeignKey(DogHouse, related_name="dogs", null=True) - - def __unicode__(self): - return u"%s" % (self.name) - - -class Bone(models.Model): - dog = models.ForeignKey(Dog, related_name='bones', null=True) - color = models.CharField(max_length=32) - - def __unicode__(self): - return u"%s" % (self.color) - - -class Forum(models.Model): - moderators = models.ManyToManyField(User, related_name='forums_moderated') - members = models.ManyToManyField(User, related_name='forums_member') - - -class Label(models.Model): - name = models.CharField(max_length=32) - - -class Job(models.Model): - name = models.CharField(max_length=200) - - -class Payment(models.Model): - scheduled = models.DateTimeField() - job = models.OneToOneField(Job, related_name="payment", null=True) - - -class Post(models.Model): - name = models.CharField(max_length=200) - label = models.ManyToManyField(Label, null=True) - - -class Order(models.Model): - name = models.CharField(max_length=200) - - -class OrderItem(models.Model): - order = models.ForeignKey(Order, related_name="items") - product = models.CharField(max_length=200) - - -class ContactGroup(models.Model): - name = models.CharField(max_length=75, blank=True, - help_text="Contact first name.") - - class Meta: - ordering = ['id'] - - def __unicode__(self): - return u'%s' % self.name - - -class Contact(models.Model): - name = models.CharField(max_length=255) - groups = models.ManyToManyField( - ContactGroup, - related_name='members', - null=True, - blank=True, - help_text="The Contact Groups this Contact belongs to." - ) - - class Meta: - ordering = ['id'] - - def __unicode__(self): - return u'%s' % self.name diff -Nru django-tastypie-0.13.3/tests/related_resource/tests.py django-tastypie-0.14.3/tests/related_resource/tests.py --- django-tastypie-0.13.3/tests/related_resource/tests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/related_resource/tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1165 +0,0 @@ -from datetime import datetime -import json - -import django -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse -from django.db.models.signals import pre_save -from django.test.testcases import TestCase - -from tastypie import fields -from tastypie.exceptions import ApiFieldError, NotFound - -from core.models import Note, MediaBit -from core.tests.mocks import MockRequest - -from related_resource.api.resources import AddressResource, CategoryResource,\ - ForumResource, FreshNoteResource, JobResource, NoteResource,\ - OrderResource, NoteWithUpdatableUserResource, PersonResource, TagResource,\ - UserResource -from related_resource.api.urls import api -from related_resource.models import Category, Label, Tag, Taggable,\ - TaggableTag, ExtraData, Company, Person, Dog, DogHouse, Bone, Product,\ - Address, Job, Payment, Forum, Order, OrderItem, Contact, ContactGroup -from testcases import TestCaseWithFixture - - -class M2MResourcesTestCase(TestCaseWithFixture): - def test_same_object_added(self): - """ - From Issue #1035 - """ - user = User.objects.create(username='gjcourt') - - ur = UserResource() - fr = ForumResource() - - resp = self.client.post(fr.get_resource_uri(), content_type='application/json', data=json.dumps({ - 'name': 'Test Forum', - 'members': [ur.get_resource_uri(user)], - 'moderators': [ur.get_resource_uri(user)], - })) - - self.assertEqual(resp.status_code, 201, resp.content) - - data = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(len(data['moderators']), 1) - self.assertEqual(len(data['members']), 1) - - -class RelatedResourceTest(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def setUp(self): - super(RelatedResourceTest, self).setUp() - self.user = User.objects.create(username="testy_mctesterson") - - def test_cannot_access_user_resource(self): - resource = api.canonical_resource_for('users') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"username": "foobar"}') - resp = resource.wrap_view('dispatch_detail')(request, pk=self.user.pk) - - self.assertEqual(resp.status_code, 405) - self.assertEqual(User.objects.get(id=self.user.id).username, self.user.username) - - def test_related_resource_authorization(self): - resource = api.canonical_resource_for('notes') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00", "author": null}') - - resp = resource.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(User.objects.get(id=self.user.id).username, 'testy_mctesterson') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body('{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-2", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00", "author": {"id": %s, "username": "foobar"}}' % self.user.id) - - resp = resource.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(User.objects.get(id=self.user.id).username, 'foobar') - - def test_ok_not_null_field_included(self): - """ - Posting a new detail with no related objects - should require one query to save the object - """ - company = Company.objects.create() - - resource = api.canonical_resource_for('product') - - request = MockRequest() - body = json.dumps({ - 'producer': {'pk': company.pk}, - }) - request.set_body(body) - - resp = resource.post_list(request) - - self.assertEqual(resp.status_code, 201) - - def test_apifielderror_missing_not_null_field(self): - """ - Posting a new detail with no related objects - should require one query to save the object - """ - resource = api.canonical_resource_for('product') - - request = MockRequest() - body = json.dumps({}) - request.set_body(body) - - with self.assertRaises(ApiFieldError): - resource.post_list(request) - - -class CategoryResourceTest(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def setUp(self): - super(CategoryResourceTest, self).setUp() - self.parent_cat_1 = Category.objects.create(parent=None, name='Dad') - self.parent_cat_2 = Category.objects.create(parent=None, name='Mom') - self.child_cat_1 = Category.objects.create(parent=self.parent_cat_1, name='Son') - self.child_cat_2 = Category.objects.create(parent=self.parent_cat_2, name='Daughter') - - def test_correct_relation(self): - resource = api.canonical_resource_for('category') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - resp = resource.wrap_view('dispatch_detail')(request, pk=self.parent_cat_1.pk) - - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(data['parent'], None) - self.assertEqual(data['name'], 'Dad') - - # Now try a child. - resp = resource.wrap_view('dispatch_detail')(request, pk=self.child_cat_2.pk) - - self.assertEqual(resp.status_code, 200) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(data['parent'], '/v1/category/2/') - self.assertEqual(data['name'], 'Daughter') - - def test_put_null(self): - resource = api.canonical_resource_for('category') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body('{"parent": null, "name": "Son"}') - - # Before the PUT, there should be a parent. - self.assertEqual(Category.objects.get(pk=self.child_cat_1.pk).parent.pk, self.parent_cat_1.pk) - - # After the PUT, the parent should be ``None``. - resp = resource.put_detail(request, pk=self.child_cat_1.pk) - self.assertEqual(resp.status_code, 204) - self.assertEqual(Category.objects.get(pk=self.child_cat_1.pk).name, 'Son') - self.assertEqual(Category.objects.get(pk=self.child_cat_1.pk).parent, None) - - -class ExplicitM2MResourceRegressionTest(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def setUp(self): - super(ExplicitM2MResourceRegressionTest, self).setUp() - self.tag_1 = Tag.objects.create(name='important') - self.taggable_1 = Taggable.objects.create(name='exam') - - # Create relations between tags and taggables through the explicit m2m table - self.taggabletag_1 = TaggableTag.objects.create(tag=self.tag_1, taggable=self.taggable_1) - - # Give each tag some extra data (the lookup of this data is what makes the test fail) - self.extradata_1 = ExtraData.objects.create(tag=self.tag_1, name='additional') - - def test_correct_setup(self): - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'GET' - - # Verify the explicit 'through' relationships has been created correctly - resource = api.canonical_resource_for('taggabletag') - resp = resource.wrap_view('dispatch_detail')(request, pk=self.taggabletag_1.pk) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['tag'], '/v1/tag/1/') - self.assertEqual(data['taggable'], '/v1/taggable/1/') - - resource = api.canonical_resource_for('taggable') - resp = resource.wrap_view('dispatch_detail')(request, pk=self.taggable_1.pk) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['name'], 'exam') - - resource = api.canonical_resource_for('tag') - request.path = "/v1/tag/%(pk)s/" % {'pk': self.tag_1.pk} - resp = resource.wrap_view('dispatch_detail')(request, pk=self.tag_1.pk) - data = json.loads(resp.content.decode('utf-8')) - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['name'], 'important') - - # and check whether the extradata is present - self.assertEqual(data['extradata']['name'], u'additional') - - def test_post_new_tag(self): - resource = api.canonical_resource_for('tag') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body('{"name": "school", "taggabletags": [ ]}') - - # Prior to the addition of ``blank=True``, this would - # fail badly. - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp.status_code, 201) - - # GET the created object (through its headers.location) - self.assertTrue(resp.has_header('location')) - location = resp['Location'] - - resp = self.client.get(location, data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 5) - self.assertEqual(deserialized['name'], 'school') - - -class OneToManySetupTestCase(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def test_one_to_many(self): - # Sanity checks. - self.assertEqual(Note.objects.count(), 2) - self.assertEqual(MediaBit.objects.count(), 0) - - fnr = FreshNoteResource() - - data = { - 'title': 'Create with related URIs', - 'slug': 'create-with-related-uris', - 'content': 'Some content here', - 'is_active': True, - 'media_bits': [ - { - 'title': 'Picture #1' - } - ] - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - - resp = fnr.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Note.objects.count(), 3) - note = Note.objects.latest('created') - self.assertEqual(note.media_bits.count(), 1) - self.assertEqual(note.media_bits.all()[0].title, u'Picture #1') - - -class FullCategoryResource(CategoryResource): - parent = fields.ToOneField('self', 'parent', null=True, full=True) - - -class RelationshipOppositeFromModelTestCase(TestCaseWithFixture): - """ - On the model, the Job relationship is defined on the Payment. - On the resource, the PaymentResource is defined on the JobResource as well - """ - def setUp(self): - super(RelationshipOppositeFromModelTestCase, self).setUp() - - # a job with a payment exists to start with - self.some_time_str = datetime.now().strftime('%Y-%m-%d %H:%M') - job = Job.objects.create(name='SomeJob') - Payment.objects.create(job=job, scheduled=self.some_time_str) - - def test_create_similar(self): - # We submit to job with the related payment included. - # Note that on the resource, the payment related resource is defined - # On the model, the Job class does not have a payment field, - # but it has a reverse relationship defined by the Payment class - resource = JobResource() - data = { - 'name': 'OtherJob', - 'payment': { - 'scheduled': self.some_time_str - } - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - - resp = resource.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Job.objects.count(), 2) - self.assertEqual(Payment.objects.count(), 2) - - new_job = Job.objects.all().order_by('-id')[0] - new_payment = Payment.objects.all().order_by('-id')[0] - - self.assertEqual(new_job.name, 'OtherJob') - self.assertEqual(new_job, new_payment.job) - - -class RelatedPatchTestCase(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def test_patch_to_one(self): - resource = FullCategoryResource() - cat1 = Category.objects.create(name='Dad') - cat2 = Category.objects.create(parent=cat1, name='Child') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request.path = "/v1/category/%(pk)s/" % {'pk': cat2.pk} - - data = { - 'name': 'Kid' - } - - request.set_body(json.dumps(data)) - - self.assertEqual(cat2.name, 'Child') - - resp = resource.patch_detail(request, pk=cat2.pk) - - self.assertEqual(resp.status_code, 202) - - cat2 = Category.objects.get(pk=2) - - self.assertEqual(cat2.name, 'Kid') - - def test_patch_detail_with_missing_related_fields(self): - """ - When fields are excluded the value of the field should not be set to a - default value if updated by tastypie. - """ - resource = NoteWithUpdatableUserResource() - note = Note.objects.create(author_id=1) - user = User.objects.get(pk=1) - - self.assertEqual(user.password, 'this_is_not_a_valid_password_string') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request.path = "/v1/noteswithupdatableuser/%(pk)s/" % {'pk': note.pk} - - data = { - 'author': { - 'id': 1, - 'username': 'johndoe', - 'email': 'john@doetown.com', - } - } - - request.set_body(json.dumps(data)) - - resp = resource.patch_detail(request, pk=note.pk) - - self.assertEqual(resp.status_code, 202) - - user2 = User.objects.get(pk=1) - - self.assertEqual(user2.email, 'john@doetown.com') - self.assertEqual(user2.password, 'this_is_not_a_valid_password_string') - - def test_patch_detail_dont_update_related_without_permission(self): - """ - When fields are excluded the value of the field should not be set to a - default value if updated by tastypie. - """ - resource = NoteResource() - note = Note.objects.create(author_id=1) - user = User.objects.get(pk=1) - - self.assertEqual(user.password, 'this_is_not_a_valid_password_string') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PATCH' - request.path = "/v1/note/%(pk)s/" % {'pk': note.pk} - - data = { - 'author': { - 'id': 1, - 'username': 'johndoe', - 'email': 'john@doetown.com', - } - } - - request.set_body(json.dumps(data)) - - resp = resource.patch_detail(request, pk=note.pk) - - self.assertEqual(resp.status_code, 202) - - user2 = User.objects.get(pk=1) - - self.assertEqual(user2.email, 'john@doetown.com') - self.assertEqual(user2.password, 'this_is_not_a_valid_password_string') - - -class NestedRelatedResourceTest(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def test_one_to_one(self): - """ - Test a related ToOne resource with a nested full ToOne resource - """ - self.assertEqual(Person.objects.count(), 0) - self.assertEqual(Company.objects.count(), 0) - self.assertEqual(Address.objects.count(), 0) - - pr = PersonResource() - - data = { - 'name': 'Joan Rivers', - 'company': { - 'name': 'Yum Yum Pie Factory!', - 'address': { - 'line': 'Somewhere, Utah' - } - } - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - resp = pr.post_list(request) - self.assertEqual(resp.status_code, 201) - - pk = Person.objects.all()[0].pk - request = MockRequest() - request.method = 'GET' - request.path = reverse('api_dispatch_detail', kwargs={ - 'pk': pk, - 'resource_name': pr._meta.resource_name, - 'api_name': pr._meta.api_name - }) - resp = pr.get_detail(request, pk=pk) - self.assertEqual(resp.status_code, 200) - - person = json.loads(resp.content.decode('utf-8')) - self.assertEqual(person['name'], 'Joan Rivers') - - company = person['company'] - self.assertEqual(company['name'], 'Yum Yum Pie Factory!') - - address = company['address'] - self.assertEqual(address['line'], 'Somewhere, Utah') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.path = reverse('api_dispatch_detail', kwargs={ - 'pk': pk, - 'resource_name': pr._meta.resource_name, - 'api_name': pr._meta.api_name - }) - request.set_body(resp.content.decode('utf-8')) - resp = pr.put_detail(request, pk=pk) - self.assertEqual(resp.status_code, 204) - - def test_one_to_many(self): - """ - Test a related ToOne resource with a nested full ToMany resource - """ - self.assertEqual(Person.objects.count(), 0) - self.assertEqual(Company.objects.count(), 0) - self.assertEqual(Product.objects.count(), 0) - - pr = PersonResource() - - data = { - 'name': 'Joan Rivers', - 'company': { - 'name': 'Yum Yum Pie Factory!', - 'products': [ - { - 'name': 'Tasty Pie' - } - ] - } - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - resp = pr.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Person.objects.count(), 1) - self.assertEqual(Company.objects.count(), 1) - self.assertEqual(Product.objects.count(), 1) - - pk = Person.objects.all()[0].pk - request = MockRequest() - request.method = 'GET' - resp = pr.get_detail(request, pk=pk) - self.assertEqual(resp.status_code, 200) - - person = json.loads(resp.content.decode('utf-8')) - self.assertEqual(person['name'], 'Joan Rivers') - - company = person['company'] - self.assertEqual(company['name'], 'Yum Yum Pie Factory!') - self.assertEqual(len(company['products']), 1) - - product = company['products'][0] - self.assertEqual(product['name'], 'Tasty Pie') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body(json.dumps(person)) - resp = pr.put_detail(request, pk=pk) - self.assertEqual(resp.status_code, 204) - - def test_many_to_one(self): - """ - Test a related ToMany resource with a nested full ToOne resource - """ - self.assertEqual(Person.objects.count(), 0) - self.assertEqual(Dog.objects.count(), 0) - self.assertEqual(DogHouse.objects.count(), 0) - - pr = PersonResource() - - data = { - 'name': 'Joan Rivers', - 'dogs': [ - { - 'name': 'Snoopy', - 'house': { - 'color': 'Red' - } - } - ] - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - resp = pr.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Person.objects.count(), 1) - self.assertEqual(Dog.objects.count(), 1) - self.assertEqual(DogHouse.objects.count(), 1) - - pk = Person.objects.all()[0].pk - request = MockRequest() - request.method = 'GET' - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.get_detail(request, pk=pk) - self.assertEqual(resp.status_code, 200) - - person = json.loads(resp.content.decode('utf-8')) - self.assertEqual(person['name'], 'Joan Rivers') - self.assertEqual(len(person['dogs']), 1) - - dog = person['dogs'][0] - self.assertEqual(dog['name'], 'Snoopy') - - house = dog['house'] - self.assertEqual(house['color'], 'Red') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body(json.dumps(person)) - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.put_detail(request, pk=pk) - self.assertEqual(resp.status_code, 204) - - def test_many_to_one_extra_data_ignored(self): - """ - Test a related ToMany resource with a nested full ToOne resource - - FieldError would result when extra data is included on an embedded - resource for an already saved object. - """ - self.assertEqual(Person.objects.count(), 0) - self.assertEqual(Dog.objects.count(), 0) - self.assertEqual(DogHouse.objects.count(), 0) - - pr = PersonResource() - - data = { - 'name': 'Joan Rivers', - 'dogs': [ - { - 'name': 'Snoopy', - 'house': { - 'color': 'Red' - } - } - ] - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - resp = pr.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Person.objects.count(), 1) - self.assertEqual(Dog.objects.count(), 1) - self.assertEqual(DogHouse.objects.count(), 1) - - pk = Person.objects.all()[0].pk - request = MockRequest() - request.method = 'GET' - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.get_detail(request, pk=pk) - self.assertEqual(resp.status_code, 200) - - person = json.loads(resp.content.decode('utf-8')) - self.assertEqual(person['name'], 'Joan Rivers') - self.assertEqual(len(person['dogs']), 1) - - dog = person['dogs'][0] - self.assertEqual(dog['name'], 'Snoopy') - - house = dog['house'] - self.assertEqual(house['color'], 'Red') - - # clients may include extra data, which should be ignored. Make extra data is ignored on the resource and sub resources. - person['thisfieldshouldbeignored'] = 'foobar' - person['dogs'][0]['thisfieldshouldbeignored'] = 'foobar' - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body(json.dumps(person)) - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.put_detail(request, pk=pk) - self.assertEqual(resp.status_code, 204) - - def test_many_to_many(self): - """ - Test a related ToMany resource with a nested full ToMany resource - """ - self.assertEqual(Person.objects.count(), 0) - self.assertEqual(Dog.objects.count(), 0) - self.assertEqual(Bone.objects.count(), 0) - - pr = PersonResource() - - data = { - 'name': 'Joan Rivers', - 'dogs': [ - { - 'name': 'Snoopy', - 'bones': [ - { - 'color': 'white' - } - ] - } - ] - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.path = reverse('api_dispatch_list', kwargs={'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - request.set_body(json.dumps(data)) - resp = pr.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Person.objects.count(), 1) - self.assertEqual(Dog.objects.count(), 1) - self.assertEqual(Bone.objects.count(), 1) - - pk = Person.objects.all()[0].pk - request = MockRequest() - request.method = 'GET' - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.get_detail(request, pk=pk) - self.assertEqual(resp.status_code, 200) - - person = json.loads(resp.content.decode('utf-8')) - self.assertEqual(person['name'], 'Joan Rivers') - self.assertEqual(len(person['dogs']), 1) - - dog = person['dogs'][0] - self.assertEqual(dog['name'], 'Snoopy') - self.assertEqual(len(dog['bones']), 1) - - bone = dog['bones'][0] - self.assertEqual(bone['color'], 'white') - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request.set_body(json.dumps(person)) - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.put_detail(request, pk=pk) - self.assertEqual(resp.status_code, 204) - - def test_many_to_many_change_nested(self): - """ - Test a related ToMany resource with a nested full ToMany resource - """ - self.assertEqual(Person.objects.count(), 0) - self.assertEqual(Dog.objects.count(), 0) - self.assertEqual(Bone.objects.count(), 0) - - pr = PersonResource() - - person = Person.objects.create(name='Joan Rivers') - dog = person.dogs.create(name='Snoopy') - bone = dog.bones.create(color='white') - - pk = person.pk - request = MockRequest() - request.method = 'GET' - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.get_detail(request, pk=pk) - self.assertEqual(resp.status_code, 200) - - data = json.loads(resp.content.decode('utf-8')) - - self.assertEqual(data['dogs'][0]['bones'][0]['color'], 'white') - - # Change just a nested resource via PUT - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - data['dogs'][0]['bones'][0]['color'] = 'gray' - body = json.dumps(data) - request.set_body(body) - request.path = reverse('api_dispatch_detail', kwargs={'pk': pk, 'resource_name': pr._meta.resource_name, 'api_name': pr._meta.api_name}) - resp = pr.put_detail(request, pk=pk) - self.assertEqual(resp.status_code, 204) - - self.assertEqual(Bone.objects.count(), 1) - bone = Bone.objects.all()[0] - self.assertEqual(bone.color, 'gray') - - -class RelatedSaveCallsTest(TestCaseWithFixture): - urls = 'related_resource.api.urls' - - def test_one_query_for_post_list(self): - """ - Posting a new detail with no related objects - should require one query to save the object - """ - resource = api.canonical_resource_for('category') - - request = MockRequest() - body = json.dumps({ - 'name': 'Foo', - 'parent': None - }) - request.set_body(body) - - with self.assertNumQueries(1): - resource.post_list(request) - - def test_two_queries_for_post_list(self): - """ - Posting a new detail with one related object, referenced via its - ``resource_uri`` should require two queries: one to save the - object, and one to lookup the related object. - """ - parent = Category.objects.create(name='Bar') - resource = api.canonical_resource_for('category') - - request = MockRequest() - body = json.dumps({ - 'name': 'Foo', - 'parent': resource.get_resource_uri(parent) - }) - - request.set_body(body) - - with self.assertNumQueries(2): - resource.post_list(request) - - def test_no_save_m2m_unchanged(self): - """ - Posting a new detail with a related m2m object shouldn't - save the m2m object unless the m2m object is provided inline. - """ - def _save_fails_test(sender, **kwargs): - self.fail("Should not have saved Label") - - pre_save.connect(_save_fails_test, sender=Label) - l1 = Label.objects.get(name='coffee') - resource = api.canonical_resource_for('post') - label_resource = api.canonical_resource_for('label') - - request = MockRequest() - - body = json.dumps({ - 'name': 'test post', - 'label': [label_resource.get_resource_uri(l1)], - }) - - request.set_body(body) - - resource.post_list(request) # _save_fails_test will explode if Label is saved - - def test_save_m2m_changed(self): - """ - Posting a new or updated detail object with a related m2m object - should save the m2m object if it's included inline. - """ - resource = api.canonical_resource_for('tag') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - body_dict = { - 'name': 'school', - 'taggabletags': [{'extra': 7}] - } - - request.set_body(json.dumps(body_dict)) - - with self.assertNumQueries(4): - resp = resource.wrap_view('dispatch_list')(request) - self.assertEqual(resp.status_code, 201) - - # 'extra' should have been set - tag = Tag.objects.all()[0] - taggable_tag = tag.taggabletags.all()[0] - self.assertEqual(taggable_tag.extra, 7) - - body_dict['taggabletags'] = [{'extra': 1234}] - - request.set_body(json.dumps(body_dict)) - - request.path = reverse('api_dispatch_detail', kwargs={ - 'pk': tag.pk, - 'resource_name': resource._meta.resource_name, - 'api_name': resource._meta.api_name - }) - - with self.assertNumQueries(5): - resource.put_detail(request) - - # 'extra' should have changed - tag = Tag.objects.all()[0] - taggable_tag = tag.taggabletags.all()[0] - self.assertEqual(taggable_tag.extra, 1234) - - def test_no_save_m2m_unchanged_existing_data_persists(self): - """ - Data should persist when posting an updated detail object with - unchanged reverse related objects. - """ - person = Person.objects.create(name='Ryan') - dog = Dog.objects.create(name='Wilfred', owner=person) - bone1 = Bone.objects.create(color='White', dog=dog) - bone2 = Bone.objects.create(color='Grey', dog=dog) - - self.assertEqual(dog.bones.count(), 2) - - resource = api.canonical_resource_for('dog') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request._load_post_and_files = lambda *args, **kwargs: None - body_dict = { - 'id': dog.id, - 'name': 'Wilfred', - 'bones': [ - {'id': bone1.id, 'color': bone1.color}, - {'id': bone2.id, 'color': bone2.color} - ] - } - - request.set_body(json.dumps(body_dict)) - - with self.assertNumQueries(13 if django.VERSION >= (1, 9) else 14): - resp = resource.wrap_view('dispatch_detail')(request, pk=dog.pk) - - self.assertEqual(resp.status_code, 204) - - dog = Dog.objects.all()[0] - - dog_bones = dog.bones.all() - - self.assertEqual(len(dog_bones), 2) - - self.assertEqual(dog_bones[0], bone1) - self.assertEqual(dog_bones[1], bone2) - - def test_no_save_m2m_related(self): - """ - When saving an object with a M2M field, don't save that related object's related objects. - """ - cg1 = ContactGroup.objects.create(name='The Inebriati') - cg2 = ContactGroup.objects.create(name='The Stone Cutters') - - c1 = Contact.objects.create(name='foo') - c2 = Contact.objects.create(name='bar') - c2.groups.add(cg1, cg2) - c3 = Contact.objects.create(name='baz') - c3.groups.add(cg1) - - self.assertEqual(list(c1.groups.all()), []) - self.assertEqual(list(c2.groups.all()), [cg1, cg2]) - self.assertEqual(list(c3.groups.all()), [cg1]) - - data = { - 'name': c1.name, - 'groups': [reverse('api_dispatch_detail', kwargs={'api_name': 'v1', 'resource_name': 'contactgroup', 'pk': cg1.pk})], - } - - resource = api.canonical_resource_for('contact') - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'PUT' - request._load_post_and_files = lambda *args, **kwargs: None - request.set_body(json.dumps(data)) - - with self.assertNumQueries(8): - response = resource.wrap_view('dispatch_detail')(request, pk=c1.pk) - - self.assertEqual(response.status_code, 204, response.content) - - new_contacts = Contact.objects.all() - new_c1 = new_contacts[0] - new_c2 = new_contacts[1] - new_c3 = new_contacts[2] - - self.assertEqual(new_c1.name, c1.name) - - self.assertEqual(new_c1.id, c1.id) - self.assertEqual(list(new_c1.groups.all()), [cg1]) - self.assertEqual(new_c2.id, c2.id) - self.assertEqual(list(new_c2.groups.all()), [cg1, cg2]) - self.assertEqual(new_c3.id, c3.id) - self.assertEqual(list(new_c3.groups.all()), [cg1]) - - new_cg1 = ContactGroup.objects.get(id=cg1.id) - new_cg2 = ContactGroup.objects.get(id=cg2.id) - - self.assertEqual(list(new_cg1.members.all()), [new_c1, new_c2, new_c3]) - self.assertEqual(list(new_cg2.members.all()), [new_c2]) - - -class CorrectUriRelationsTestCase(TestCaseWithFixture): - """ - Validate that incorrect URI (with PKs that line up to valid data) are not - accepted. - """ - urls = 'related_resource.api.urls' - - def test_incorrect_uri(self): - self.assertEqual(Note.objects.count(), 2) - nr = NoteResource() - - # For this test, we need a ``User`` with the same PK as a ``Note``. - note_1 = Note.objects.latest('created') - User.objects.create( - id=note_1.pk, - username='valid', - email='valid@exmaple.com', - password='junk' - ) - - data = { - # This URI is flat-out wrong (wrong resource). - # This should cause the request to fail. - 'author': '/v1/notes/{0}/'.format( - note_1.pk - ), - 'title': 'Nopenopenope', - 'slug': 'invalid-request', - 'content': "This shouldn't work.", - 'is_active': True, - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - request.set_body(json.dumps(data)) - - with self.assertRaises(NotFound) as cm: - nr.post_list(request) - - self.assertEqual(str(cm.exception), "An incorrect URL was provided '/v1/notes/2/' for the 'UserResource' resource.") - self.assertEqual(Note.objects.count(), 2) - - -class PrefetchRelatedTests(TestCase): - def setUp(self): - self.forum = Forum.objects.create() - self.resource = api.canonical_resource_for('forum') - - self.user_data = [ - { - 'username': 'valid but unique', - 'email': 'valid.unique@exmaple.com', - 'password': 'junk', - }, - { - 'username': 'valid and very unique', - 'email': 'valid.very.unique@exmaple.com', - 'password': 'junk', - }, - { - 'username': 'valid again', - 'email': 'valid.very.unique@exmaple.com', - 'password': 'junk', - }, - ] - - def tearDown(self): - usernames = [data['username'] for data in self.user_data] - User.objects.filter(username__in=usernames).delete() - self.forum.delete() - - def make_request(self, method): - request = MockRequest() - request.GET = {'format': 'json'} - request.method = method - request.set_body(json.dumps({ - 'members': [ - self.user_data[0], - self.user_data[1], - ], - 'moderators': [self.user_data[2]], - })) - request.path = reverse('api_dispatch_detail', kwargs={ - 'pk': self.forum.pk, - 'resource_name': self.resource._meta.resource_name, - 'api_name': self.resource._meta.api_name - }) - - return request - - def test_m2m_put(self): - request = self.make_request('PUT') - response = self.resource.put_detail(request) - - self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode('utf-8')) - - # Check that the query does what it's supposed to - # and only the return value is wrong - self.assertEqual(User.objects.count(), 3) - - self.assertEqual(len(data['members']), 2) - self.assertEqual(len(data['moderators']), 1) - - def test_m2m_patch(self): - request = self.make_request('PATCH') - response = self.resource.patch_detail(request) - - self.assertEqual(response.status_code, 202) - data = json.loads(response.content.decode('utf-8')) - - # Check that the query does what it's supposed to - # and only the return value is wrong - self.assertEqual(User.objects.count(), 3) - - self.assertEqual(len(data['members']), 2) - self.assertEqual(len(data['moderators']), 1) - - -class ModelWithReverseItemsRelationshipTest(TestCase): - def test_reverse_items_relationship(self): - order_resource = OrderResource() - - data = { - 'name': 'order1', - 'items': [ - { - 'name': 'car', - }, - { - 'name': 'yacht', - } - ] - } - - request = MockRequest() - request.GET = {'format': 'json'} - request.method = 'POST' - - request.path = reverse('api_dispatch_list', - kwargs={'resource_name': order_resource._meta.resource_name, - 'api_name': order_resource._meta.api_name}) - request.set_body(json.dumps(data)) - resp = order_resource.post_list(request) - self.assertEqual(resp.status_code, 201) - self.assertEqual(Order.objects.count(), 1) - self.assertEqual(OrderItem.objects.count(), 2) - - -class OneToOneTestCase(TestCase): - def test_reverse_one_to_one_post(self): - ed = ExtraData.objects.create(name='ed_name') - resource = TagResource() - - # Post the extradata element which is attached to a "reverse" OneToOne - request = MockRequest() - request.method = "POST" - request.body = json.dumps({ - "name": "tag_name", - "tagged": [], - "extradata": "/v1/extradata/%s/" % ed.pk - }) - - resp = resource.post_list(request) - # Assert that the status code is CREATED - self.assertEqual(resp.status_code, 201) - - tag = Tag.objects.get(pk=int(resp['Location'].split("/")[-2])) - self.assertEqual(tag.extradata, ed) - - @staticmethod - def patch_details(resource, pk, **kwargs): - # Post the extradata element which is attached to a "reverse" OneToOne - request = MockRequest() - request.method = "PATCH" - request.body = json.dumps(kwargs) - response = resource.patch_detail(request, pk=pk) - return response - - def test_one_to_one_two_patches_in_a_row(self): - resource = TagResource() - ed = ExtraData.objects.create(name='ed_name') - tag = Tag.objects.create(name='tag_name') - tag2 = Tag.objects.create(name="tag_name2") - extra_data = "/v1/extradata/%s/" % ed.pk - - self.patch_details(resource, tag.pk, extradata=extra_data) - resp = self.patch_details(resource, tag2.pk, name="new_tag_name") - - self.assertEqual(resp.status_code, 202) - self.assertEqual(Tag.objects.get(pk=tag2.pk).name, "new_tag_name") - - def test_toonefield_spanning_a_relationship(self): - """ - #1446 - """ - # just need to be able to add this to a class - class CustomPersonResource(PersonResource): - company_address = fields.ToOneField(AddressResource, - 'company__address', null=True, full=True) - resource = CustomPersonResource() - resource.fields['company_address'] diff -Nru django-tastypie-0.13.3/tests/requirements.txt django-tastypie-0.14.3/tests/requirements.txt --- django-tastypie-0.13.3/tests/requirements.txt 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/requirements.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ --r ../requirements.txt -biplist -coverage -defusedxml -lxml -mock<1.1.0 -pytz==2013b -PyYAML diff -Nru django-tastypie-0.13.3/tests/run_all_tests.sh django-tastypie-0.14.3/tests/run_all_tests.sh --- django-tastypie-0.13.3/tests/run_all_tests.sh 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/run_all_tests.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,46 +0,0 @@ -#!/bin/bash - -PYTHONPATH=$PWD:$PWD/..${PYTHONPATH:+:$PYTHONPATH} -export PYTHONPATH - -VERSION=`django-admin.py --version` -arrIN=(${VERSION//./ }) -major=${arrIN[0]} -minor=${arrIN[1]} - -ALL="core customuser basic alphanumeric slashless namespaced related validation gis gis_spatialite content_gfk authorization" - -if [ $# -eq 0 ]; then - PYTESTPATHS=$ALL -elif [ $1 == '-h' ]; then - echo "Valid arguments are: $ALL" -else - PYTESTPATHS=$@ -fi - -for pytestpath in $PYTESTPATHS; do - IFS='.' read -r type type_remainder <<< "$pytestpath" - - echo "** $type **" - module_name=$type - - if [ $type == 'related' ]; then - module_name=${module_name}_resource - elif [ $type == 'gis_spatialite' ]; then - module_name='gis' - fi - - test_name=$module_name - if [ -n "$type_remainder" ]; then - test_name=$test_name.$type_remainder - fi - - if [ $type == 'gis' ]; then - createdb -T template_postgis tastypie.db - elif [ $type == 'gis_spatialite' ]; then - spatialite tastypie-spatialite.db "SELECT InitSpatialMetaData();" - fi - - ./manage_$type.py test $test_name.tests --traceback - echo; echo -done diff -Nru django-tastypie-0.13.3/tests/settings_alphanumeric.py django-tastypie-0.14.3/tests/settings_alphanumeric.py --- django-tastypie-0.13.3/tests/settings_alphanumeric.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_alphanumeric.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('alphanumeric') - -ROOT_URLCONF = 'alphanumeric.urls' diff -Nru django-tastypie-0.13.3/tests/settings_authorization.py django-tastypie-0.14.3/tests/settings_authorization.py --- django-tastypie-0.13.3/tests/settings_authorization.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_authorization.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -from settings import * # flake8: noqa - -INSTALLED_APPS.append('django.contrib.sites') -INSTALLED_APPS.append('authorization') - -ROOT_URLCONF = 'authorization.urls' diff -Nru django-tastypie-0.13.3/tests/settings_basic.py django-tastypie-0.14.3/tests/settings_basic.py --- django-tastypie-0.13.3/tests/settings_basic.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_basic.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('django.contrib.sessions') -INSTALLED_APPS.append('basic') - -ROOT_URLCONF = 'basic.urls' diff -Nru django-tastypie-0.13.3/tests/settings_content_gfk.py django-tastypie-0.14.3/tests/settings_content_gfk.py --- django-tastypie-0.13.3/tests/settings_content_gfk.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_content_gfk.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -from settings import * # flake8: noqa - -INSTALLED_APPS.append('content_gfk') - -ROOT_URLCONF = 'content_gfk.urls' diff -Nru django-tastypie-0.13.3/tests/settings_core.py django-tastypie-0.14.3/tests/settings_core.py --- django-tastypie-0.13.3/tests/settings_core.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_core.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('django.contrib.sessions') -INSTALLED_APPS.append('core') - -try: - import oauth_provider # flake8: noqa - INSTALLED_APPS.append('oauth_provider') -except ImportError: - pass - -ROOT_URLCONF = 'core.tests.api_urls' -MEDIA_URL = 'http://localhost:8080/media/' - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'handlers': { - 'simple': { - 'level': 'ERROR', - 'class': 'core.utils.SimpleHandler', - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['simple'], - 'level': 'ERROR', - 'propagate': False, - }, - } -} diff -Nru django-tastypie-0.13.3/tests/settings_customuser.py django-tastypie-0.14.3/tests/settings_customuser.py --- django-tastypie-0.13.3/tests/settings_customuser.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_customuser.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('customuser') - -ROOT_URLCONF = 'core.tests.api_urls' -MEDIA_URL = 'http://localhost:8080/media/' - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'handlers': { - 'simple': { - 'level': 'ERROR', - 'class': 'core.utils.SimpleHandler', - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['simple'], - 'level': 'ERROR', - 'propagate': False, - }, - } -} - -AUTH_USER_MODEL = 'customuser.CustomUser' diff -Nru django-tastypie-0.13.3/tests/settings_gis.py django-tastypie-0.14.3/tests/settings_gis.py --- django-tastypie-0.13.3/tests/settings_gis.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_gis.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('gis') - -# We just hardcode postgis here. -DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': DATABASE_NAME, - } -} - -ROOT_URLCONF = 'gis.urls' diff -Nru django-tastypie-0.13.3/tests/settings_gis_spatialite.py django-tastypie-0.14.3/tests/settings_gis_spatialite.py --- django-tastypie-0.13.3/tests/settings_gis_spatialite.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_gis_spatialite.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -from settings_gis import * # flake8: noqa - -# Run `spatialite tastypie-spatialite.db "SELECT InitSpatialMetaData();"` before -# trying spatialite on disk. -# "InitSpatiaMetaData ()error:"table spatial_ref_sys already exists" can be ignored. -DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.spatialite' -DATABASES['default']['NAME'] = 'tastypie-spatialite.db' diff -Nru django-tastypie-0.13.3/tests/settings_namespaced.py django-tastypie-0.14.3/tests/settings_namespaced.py --- django-tastypie-0.13.3/tests/settings_namespaced.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_namespaced.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('basic') -INSTALLED_APPS.append('namespaced') - -ROOT_URLCONF = 'namespaced.api.urls' diff -Nru django-tastypie-0.13.3/tests/settings_profilingtests.py django-tastypie-0.14.3/tests/settings_profilingtests.py --- django-tastypie-0.13.3/tests/settings_profilingtests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_profilingtests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('django.contrib.sessions') -INSTALLED_APPS.append('profilingtests') - -try: - import oauth_provider # flake8: noqa - INSTALLED_APPS.append('oauth_provider') -except ImportError: - pass - -ROOT_URLCONF = 'profilingtests.urls' -MEDIA_URL = 'http://localhost:8080/media/' - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'handlers': { - 'simple': { - 'level': 'ERROR', - 'class': 'core.utils.SimpleHandler', - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['simple'], - 'level': 'ERROR', - 'propagate': False, - }, - } -} diff -Nru django-tastypie-0.13.3/tests/settings.py django-tastypie-0.14.3/tests/settings.py --- django-tastypie-0.13.3/tests/settings.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -import os -import sys - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -ADMINS = ( - ('test@example.com', 'Mr. Test'), -) - -SITE_ID = 1 - -BASE_PATH = os.path.abspath(os.path.dirname(__file__)) - -MEDIA_ROOT = os.path.normpath(os.path.join(BASE_PATH, 'media')) - -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = 'tastypie.db' -TEST_DATABASE_NAME = '' - -# for forwards compatibility -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.%s' % DATABASE_ENGINE, - 'NAME': DATABASE_NAME, - 'TEST_NAME': TEST_DATABASE_NAME, - } -} - - -INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'tastypie', -] - -DEBUG = True -TEMPLATE_DEBUG = DEBUG -CACHE_BACKEND = 'locmem://' -SECRET_KEY = 'verysecret' - -# weaker password hashing shoulod allow for faster tests -PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.CryptPasswordHasher', - 'django.contrib.auth.hashers.PBKDF2PasswordHasher', - 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', - 'django.contrib.auth.hashers.BCryptPasswordHasher', - 'django.contrib.auth.hashers.SHA1PasswordHasher', - 'django.contrib.auth.hashers.MD5PasswordHasher', -) - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'loggers': { - 'py.warnings': { - 'level': 'ERROR', # change to WARNING to show DeprecationWarnings, etc. - }, - }, -} - -TASTYPIE_FULL_DEBUG = False - -USE_TZ = True - -MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) diff -Nru django-tastypie-0.13.3/tests/settings_related.py django-tastypie-0.14.3/tests/settings_related.py --- django-tastypie-0.13.3/tests/settings_related.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_related.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('core') -INSTALLED_APPS.append('related_resource') - -ROOT_URLCONF = 'related_resource.api.urls' diff -Nru django-tastypie-0.13.3/tests/settings_slashless.py django-tastypie-0.14.3/tests/settings_slashless.py --- django-tastypie-0.13.3/tests/settings_slashless.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_slashless.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('basic') -INSTALLED_APPS.append('slashless') - -ROOT_URLCONF = 'slashless.api.urls' - -APPEND_SLASH = False -TASTYPIE_ALLOW_MISSING_SLASH = True diff -Nru django-tastypie-0.13.3/tests/settings_validation.py django-tastypie-0.14.3/tests/settings_validation.py --- django-tastypie-0.13.3/tests/settings_validation.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/settings_validation.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -from settings import * # flake8: noqa -INSTALLED_APPS.append('basic') -INSTALLED_APPS.append('validation') - -ROOT_URLCONF = 'validation.api.urls' diff -Nru django-tastypie-0.13.3/tests/slashless/api/resources.py django-tastypie-0.14.3/tests/slashless/api/resources.py --- django-tastypie-0.13.3/tests/slashless/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/slashless/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -from django.contrib.auth.models import User -from tastypie import fields -from tastypie.resources import ModelResource -from tastypie.authorization import Authorization -from basic.models import Note - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - authorization = Authorization() - - -class NoteResource(ModelResource): - user = fields.ForeignKey(UserResource, 'user') - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - authorization = Authorization() diff -Nru django-tastypie-0.13.3/tests/slashless/api/urls.py django-tastypie-0.14.3/tests/slashless/api/urls.py --- django-tastypie-0.13.3/tests/slashless/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/slashless/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -from django.conf.urls import include, url -from tastypie.api import Api -from slashless.api.resources import NoteResource, UserResource - - -api = Api(api_name='v1') -api.register(NoteResource(), canonical=True) -api.register(UserResource(), canonical=True) - -urlpatterns = [ - url(r'^api/', include(api.urls)), -] diff -Nru django-tastypie-0.13.3/tests/slashless/fixtures/test_data.json django-tastypie-0.14.3/tests/slashless/fixtures/test_data.json --- django-tastypie-0.13.3/tests/slashless/fixtures/test_data.json 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/slashless/fixtures/test_data.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -[ - { - "fields": { - "username": "johndoe", - "email": "john@doe.com", - "password": "this_is_not_a_valid_password_string" - }, - "model": "auth.user", - "pk": 1 - }, - - { - "fields": { - "user": 1, - "title": "First Post!", - "slug": "first-post", - "content": "This is my very first post using my shiny new API. Pretty sweet, huh?", - "is_active": true, - "created": "2010-03-30 20:05:00", - "updated": "2010-03-30 20:05:00" - }, - "model": "basic.note", - "pk": 1 - }, - { - "fields": { - "user": 1, - "title": "Another Post", - "slug": "another-post", - "content": "The dog ate my cat today. He looks seriously uncomfortable.", - "is_active": true, - "created": "2010-03-31 20:05:00", - "updated": "2010-03-31 20:05:00" - }, - "model": "basic.note", - "pk": 2 - } -] diff -Nru django-tastypie-0.13.3/tests/slashless/tests.py django-tastypie-0.14.3/tests/slashless/tests.py --- django-tastypie-0.13.3/tests/slashless/tests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/slashless/tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -import json - -from django.conf import settings - -from testcases import TestCaseWithFixture - - -class ViewsWithoutSlashesTestCase(TestCaseWithFixture): - urls = 'slashless.api.urls' - - def setUp(self): - super(ViewsWithoutSlashesTestCase, self).setUp() - self.old_debug = settings.DEBUG - settings.DEBUG = True - - def tearDown(self): - settings.DEBUG = self.old_debug - super(ViewsWithoutSlashesTestCase, self).tearDown() - - def test_gets_without_trailing_slash(self): - resp = self.client.get('/api/v1', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['notes'], {'list_endpoint': '/api/v1/notes', 'schema': '/api/v1/notes/schema'}) - - resp = self.client.get('/api/v1/notes', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 2) - self.assertEqual(deserialized['meta']['limit'], 20) - self.assertEqual(len(deserialized['objects']), 2) - self.assertEqual([obj['title'] for obj in deserialized['objects']], [u'First Post!', u'Another Post']) - - resp = self.client.get('/api/v1/notes/1', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized), 9) - self.assertEqual(deserialized['title'], u'First Post!') - - resp = self.client.get('/api/v1/notes/set/2;1', data={'format': 'json'}) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - obj_ids = [o["id"] for o in deserialized["objects"]] - self.assertEqual(sorted(obj_ids), [1, 2]) diff -Nru django-tastypie-0.13.3/tests/testcases.py django-tastypie-0.14.3/tests/testcases.py --- django-tastypie-0.13.3/tests/testcases.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/testcases.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ -import socket -import threading - -import django -from django.core.handlers.wsgi import WSGIHandler -from django.core.management import call_command -from django.core.servers import basehttp -from django.db import connections -from django.test.testcases import TransactionTestCase, TestCase - - -class StoppableWSGIServer(basehttp.WSGIServer): - """WSGIServer with short timeout, so that server thread can stop this server.""" - - def server_bind(self): - """Sets timeout to 1 second.""" - basehttp.WSGIServer.server_bind(self) - self.socket.settimeout(1) - - def get_request(self): - """Checks for timeout when getting request.""" - try: - sock, address = self.socket.accept() - sock.settimeout(None) - return (sock, address) - except socket.timeout: - raise - - -class TestServerThread(threading.Thread): - """Thread for running a http server while tests are running.""" - - def __init__(self, address, port): - self.address = address - self.port = port - self._stopevent = threading.Event() - self.started = threading.Event() - self.error = None - super(TestServerThread, self).__init__() - - def run(self): - """Sets up test server and database and loops over handling http requests.""" - try: - handler = WSGIHandler() - server_address = (self.address, self.port) - httpd = StoppableWSGIServer(server_address, basehttp.WSGIRequestHandler) - httpd.set_app(handler) - self.started.set() - except socket.error as e: - self.error = e - self.started.set() - return - - # Must do database stuff in this new thread if database in memory. - from django.conf import settings - - db = settings.DATABASES['default'] - - ENGINE = db['ENGINE'] - TEST_NAME = db.get('TEST_NAME') - - if ('sqlite3' in ENGINE or 'spatialite' in ENGINE) and\ - (not TEST_NAME or TEST_NAME == ':memory:'): - if 'spatialite' in ENGINE: - cursor = connections['default'].cursor() - - cursor.execute('SELECT InitSpatialMetaData()') - cursor.fetchone() - - if django.VERSION >= (1, 9): - call_command('migrate', run_syncdb=True, interactive=False, verbosity=0) - else: - call_command('syncdb', interactive=False, verbosity=0) - call_command('migrate', interactive=False, verbosity=0) - - # Import the fixture data into the test database. - if hasattr(self, 'fixtures'): - # We have to use this slightly awkward syntax due to the fact - # that we're using *args and **kwargs together. - call_command('loaddata', *self.fixtures, **{'verbosity': 0}) - - # Loop until we get a stop event. - while not self._stopevent.isSet(): - httpd.handle_request() - - def join(self, timeout=None): - """Stop the thread and wait for it to finish.""" - self._stopevent.set() - threading.Thread.join(self, timeout) - - -class TestServerTestCase(TransactionTestCase): - fixtures = ['test_data.json'] - - def start_test_server(self, address='localhost', port=8000): - """Creates a live test server object (instance of WSGIServer).""" - self.server_thread = TestServerThread(address, port) - self.server_thread.fixtures = self.fixtures - self.server_thread.start() - self.server_thread.started.wait() - if self.server_thread.error: - raise self.server_thread.error - - def stop_test_server(self): - if self.server_thread: - self.server_thread.join() - - -class TestCaseWithFixture(TestCase): - fixtures = ['test_data.json'] diff -Nru django-tastypie-0.13.3/tests/validation/api/resources.py django-tastypie-0.14.3/tests/validation/api/resources.py --- django-tastypie-0.13.3/tests/validation/api/resources.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/validation/api/resources.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -from django.contrib.auth.models import User -from tastypie import fields -from tastypie.constants import ALL -from tastypie.resources import ModelResource -from tastypie.authorization import Authorization -from basic.models import Note, AnnotatedNote, UserForm -from django import forms -from tastypie.validation import FormValidation - -# NOTES: -# model defaults don't matter since we are not rendering a form, if you want to -# use a default exclude the field. - - -class UserResource(ModelResource): - class Meta: - resource_name = 'users' - queryset = User.objects.all() - authorization = Authorization() - validation = FormValidation(form_class=UserForm) - - -class AnnotatedNoteForm(forms.ModelForm): - - class Meta: - model = AnnotatedNote - exclude = ('note',) - - -class AnnotatedNoteResource(ModelResource): - - class Meta: - resource_name = 'annotated' - queryset = AnnotatedNote.objects.all() - authorization = Authorization() - validation = FormValidation(form_class=AnnotatedNoteForm) - - -class NoteForm(forms.ModelForm): - - class Meta: - model = Note - exclude = ('user', 'created', 'updated') - - -class NoteResource(ModelResource): - user = fields.ForeignKey(UserResource, 'user') - annotated = fields.ForeignKey(AnnotatedNoteResource, 'annotated', - related_name='note', null=True, full=True) - - class Meta: - resource_name = 'notes' - queryset = Note.objects.all() - authorization = Authorization() - validation = FormValidation(form_class=NoteForm) - filtering = { - "created": ALL - } diff -Nru django-tastypie-0.13.3/tests/validation/api/urls.py django-tastypie-0.14.3/tests/validation/api/urls.py --- django-tastypie-0.13.3/tests/validation/api/urls.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/validation/api/urls.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -from django.conf.urls import include, url - -from tastypie.api import Api - -from validation.api.resources import NoteResource, UserResource,\ - AnnotatedNoteResource - - -api = Api(api_name='v1') -api.register(NoteResource(), canonical=True) -api.register(UserResource(), canonical=True) -api.register(AnnotatedNoteResource(), canonical=True) - -urlpatterns = [ - url(r'^api/', include(api.urls)), -] diff -Nru django-tastypie-0.13.3/tests/validation/tests.py django-tastypie-0.14.3/tests/validation/tests.py --- django-tastypie-0.13.3/tests/validation/tests.py 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tests/validation/tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,160 +0,0 @@ -import json - -from basic.models import Note -from testcases import TestCaseWithFixture - - -class FilteringErrorsTestCase(TestCaseWithFixture): - urls = 'validation.api.urls' - - def test_valid_date(self): - resp = self.client.get('/api/v1/notes/', data={ - 'format': 'json', - 'created__gte': '2010-03-31' - }) - self.assertEqual(resp.status_code, 200) - deserialized = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(deserialized['objects']), Note.objects.filter(created__gte='2010-03-31').count()) - - def test_invalid_date(self): - resp = self.client.get('/api/v1/notes/', data={ - 'format': 'json', - 'created__gte': 'foo-baz-bar' - }) - self.assertEqual(resp.status_code, 400) - - -class PostNestResouceValidationTestCase(TestCaseWithFixture): - urls = 'validation.api.urls' - - def test_valid_data(self): - data = json.dumps({ - 'title': 'Test Title', - 'slug': 'test-title', - 'content': 'This is the content', - 'user': {'pk': 1}, # loaded from fixtures - 'annotated': {'annotations': 'This is an annotations'}, - }) - - resp = self.client.post('/api/v1/notes/', data=data, content_type='application/json') - self.assertEqual(resp.status_code, 201) - note = json.loads(self.client.get(resp['location']).content.decode('utf-8')) - self.assertTrue(note['annotated']) - - def test_invalid_data(self): - data = json.dumps({ - 'title': '', - 'slug': 'test-title', - 'content': 'This is the content', - 'user': {'pk': 1}, # loaded from fixtures - 'annotated': {'annotations': ''}, - }) - - resp = self.client.post('/api/v1/notes/', data=data, content_type='application/json') - self.assertEqual(resp.status_code, 400) - self.assertEqual(json.loads(resp.content.decode('utf-8')), { - 'notes': { - 'title': ['This field is required.'] - }, - 'annotated': { - 'annotations': ['This field is required.'] - } - }) - - -class PutDetailNestResouceValidationTestCase(TestCaseWithFixture): - urls = 'validation.api.urls' - - def test_valid_data(self): - data = json.dumps({ - 'title': 'Test Title', - 'slug': 'test-title', - 'content': 'This is the content', - 'annotated': {'annotations': 'This is another annotations'}, - }) - - resp = self.client.put('/api/v1/notes/1/', data=data, content_type='application/json') - self.assertEqual(resp.status_code, 204) - note = json.loads(self.client.get('/api/v1/notes/1/', content_type='application/json').content.decode('utf-8')) - self.assertTrue(note['annotated']) - self.assertEqual('test-title', note['slug']) - - def test_invalid_data(self): - data = json.dumps({ - 'title': '', - 'slug': '', - 'content': 'This is the content', - 'annotated': {'annotations': None}, - }) - - resp = self.client.put('/api/v1/notes/1/', data=data, content_type='application/json') - self.assertEqual(resp.status_code, 400) - self.assertEqual(json.loads(resp.content.decode('utf-8')), { - 'notes': { - 'slug': ['This field is required.'], - 'title': ['This field is required.'] - }, - 'annotated': { - 'annotations': ['This field is required.'] - } - }) - - -class PutListNestResouceValidationTestCase(TestCaseWithFixture): - urls = 'validation.api.urls' - - def test_valid_data(self): - data = json.dumps({'objects': [ - { - 'id': 1, - 'title': 'Test Title', - 'slug': 'test-title', - 'content': 'This is the content', - 'annotated': {'annotations': 'This is another annotations'}, - 'user': {'id': 1} - }, - { - 'id': 2, - 'title': 'Test Title', - 'slug': 'test-title', - 'content': 'This is the content', - 'annotated': {'annotations': 'This is the third annotations'}, - 'user': {'id': 1} - } - - ]}) - - resp = self.client.put('/api/v1/notes/', data=data, content_type='application/json') - self.assertEqual(resp.status_code, 204) - note = json.loads(self.client.get('/api/v1/notes/1/', content_type='application/json').content.decode('utf-8')) - self.assertTrue(note['annotated']) - note = json.loads(self.client.get('/api/v1/notes/2/', content_type='application/json').content.decode('utf-8')) - self.assertTrue(note['annotated']) - - def test_invalid_data(self): - data = json.dumps({'objects': [ - { - 'id': 1, - 'title': 'Test Title', - 'slug': 'test-title', - 'annotated': {'annotations': None}, - 'user': {'id': 1} - }, - { - 'id': 2, - 'title': 'Test Title', - 'annotated': {'annotations': None}, - 'user': {'id': 1} - } - ]}) - - resp = self.client.put('/api/v1/notes/', data=data, content_type='application/json') - self.assertEqual(resp.status_code, 400) - self.assertEqual(json.loads(resp.content.decode('utf-8')), { - 'notes': { - 'content': ['This field is required.'] - }, - 'annotated': { - 'annotations': ['This field is required.'] - } - }) diff -Nru django-tastypie-0.13.3/TODO django-tastypie-0.14.3/TODO --- django-tastypie-0.13.3/TODO 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/TODO 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -TODO -==== - -Short Term ----------- - -Cody: -- Test support for serialization/deserialization. - -Daniel: -- Authentication - - ``OauthAuthentication`` - - ``DigestAuthentication`` -- More integration tests (intermediate, advanced, composite, non-model) - - -Long Term ---------- - -- HTML browsing diff -Nru django-tastypie-0.13.3/tox.ini django-tastypie-0.14.3/tox.ini --- django-tastypie-0.13.3/tox.ini 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/tox.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -[tox] -envlist = - py{34,27}-djdev, - py{34,27}-dj19, - py{34,27}-dj18, - py{34,27}-dj17, - py{34,27}-docs, - py27-flake8, - py27-flake8-strict -skipsdist=True - -[testenv] -usedevelop=True -test-executable = - {envbindir}/coverage run --append --source=tastypie,tests {envbindir}/django-admin.py -setenv = - PYTHONPATH = {toxinidir}:{toxinidir}/tests -commands = - dj{17,18,19,dev}: {[testenv]test-executable} test -p '*' core.tests --settings=settings_core - dj{17,18,19,dev}: {[testenv]test-executable} test basic.tests --settings=settings_basic - dj{17,18,19,dev}: {[testenv]test-executable} test related_resource.tests --settings=settings_related - dj{17,18,19,dev}: {[testenv]test-executable} test alphanumeric.tests --settings=settings_alphanumeric - dj{17,18,19,dev}: {[testenv]test-executable} test authorization.tests --settings=settings_authorization - dj{17,18,19,dev}: {[testenv]test-executable} test content_gfk.tests --settings=settings_content_gfk - dj{17,18,19,dev}: {[testenv]test-executable} test customuser.tests --settings=settings_customuser - dj{17,18,19,dev}: {[testenv]test-executable} test namespaced.tests --settings=settings_namespaced - dj{17,18,19,dev}: {[testenv]test-executable} test slashless.tests --settings=settings_slashless - dj{17,18,19,dev}: {[testenv]test-executable} test validation.tests --settings=settings_validation - dj{17,18,19,dev}: {[testenv]test-executable} test gis.tests --settings=settings_gis_spatialite - - docs: sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html - docs: sphinx-build -W -b doctest -d {envtmpdir}/doctrees . {envtmpdir}/html - - flake8: {envbindir}/flake8 --ignore=E128,E501 --exclude=.*/,tastypie/migrations/0001_initial.py . - - flake8-strict: {envbindir}/flake8 --ignore=E128 --exclude=.*/,tastypie/migrations/0001_initial.py --max-complexity 10 . -basepython = - py27: python2.7 - py34: python3.4 -deps = - dj17: Django>=1.7,<1.8 - dj18: Django>=1.8,<1.9 - dj19: Django>=1.9,<1.10 - djdev: https://github.com/django/django/archive/master.tar.gz - - py27-dj{17,18,19,dev}: python-digest - py27-dj{17,18,19,dev}: django-oauth-plus==2.2.8 - py27-dj{17,18,19,dev}: oauth2 - py27-dj{17,18,19,dev}: pysqlite==2.7.0 - py34-dj{17,18,19,dev}: python3-digest>=1.8b4 - dj{17,18,19,dev}: -r{toxinidir}/tests/requirements.txt - - docs: Sphinx - docs: Django>=1.9,<1.10 - docs: mock - docs: sphinx_rtd_theme - - {flake8,flake8-strict}: flake8 -changedir = - docs: docs/ -sitepackages = - docs: True diff -Nru django-tastypie-0.13.3/.travis.yml django-tastypie-0.14.3/.travis.yml --- django-tastypie-0.13.3/.travis.yml 2016-02-17 13:10:18.000000000 +0000 +++ django-tastypie-0.14.3/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -sudo: false - -language: python - -python: - - "2.7" - - "3.4" - -env: - - MODE=flake8 - - MODE=flake8-strict - - MODE=docs - - DJANGO_VERSION=dj17 - - DJANGO_VERSION=dj18 - - DJANGO_VERSION=dj19 - - DJANGO_VERSION=djdev - -matrix: - allow_failures: - - env: DJANGO_VERSION=djdev - - env: MODE=flake8-strict - exclude: - - python: "3.4" - env: MODE=flake8 - - python: "3.4" - env: MODE=flake8-strict - -addons: - apt: - packages: - - binutils - - libproj-dev - - gdal-bin - - spatialite-bin - - libspatialite-dev - -cache: - directories: - - $HOME/.cache/pip - -before_cache: - - rm -f $HOME/.cache/pip/log/debug.log - -# command to install dependencies -install: - - pip install -U pip - - pip install -U wheel virtualenv - - pip install tox coveralls - -after_success: - - coveralls - -# command to run tests -script: - - coverage erase - - tox -e py${TRAVIS_PYTHON_VERSION/./}-${DJANGO_VERSION}${MODE}