[
  {
    "path": ".github/workflows/ci.yml",
    "content": "on:\n  push:\n    branches:\n      - master\n  pull_request:\n\nname: CI\n\njobs:\n\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        deps:\n          - { python: '3.7', django: '~=2.2.0', drf: '~=3.10.0' }\n          - { python: '3.7', django: '~=3.2.0', drf: '~=3.11.0' }\n          - { python: '3.8', django: '~=3.2.0', drf: '~=3.12.0' }\n          - { python: '3.10', django: '~=3.2.0', drf: '~=3.12.0' }\n          - { python: '3.9', django: '~=4.0.0', drf: '~=3.13.0' }\n          - { python: '3.10', django: '~=4.0.0', drf: '~=3.13.0' }\n      fail-fast: false\n    name: Python ${{ matrix.deps.python }} / Django ${{ matrix.deps.django }} / DRF ${{ matrix.deps.drf }}\n    steps:\n      - uses: actions/checkout@v2\n      - name: Setup python ${{ matrix.deps.python }}\n        uses: actions/setup-python@v1\n        with:\n          python-version: ${{ matrix.deps.python }}\n      - name: Upgrade pip\n        run: pip install -U pip\n      - name: Install dependencies\n        run: pip install \"django${{ matrix.deps.django }}\" \"djangorestframework${{ matrix.deps.drf }}\"\n      - name: Run tests\n        run: python runtests.py\n\n  check-formatting:\n    runs-on: ubuntu-latest\n    name: Check code formatting\n    steps:\n      - uses: actions/checkout@v2\n      - name: Black Code Formatter\n        uses: lgeiger/black-action@master\n        with:\n          args: \"drf_dynamic_fields tests runtests.py --check --diff\"\n"
  },
  {
    "path": ".gitignore",
    "content": ".cache/\n.coverage\n*.swp\n*.pyc\n__pycache__\ndist/\n*.egg-info/\nbuild/\n.tox/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nThis project follows semantic versioning.\n\nPossible log types:\n\n- `[added]` for new features.\n- `[changed]` for changes in existing functionality.\n- `[deprecated]` for once-stable features removed in upcoming releases.\n- `[removed]` for deprecated features removed in this release.\n- `[fixed]` for any bug fixes.\n- `[security]` to invite users to upgrade in case of vulnerabilities.\n\n## [Unreleased]\n\n## [0.4.0] - 2022-04-05\n\n - [added] Cache `fields` property for improved performance (#33, #35)\n - [fixed] Fix warning message typo (#34)\n - [changed] Python 2 support dropped\n - [changed] Django <2.2 support dropped\n\n## [0.3.1] - 2019-03-01\n\n - [added] Allow suppressing context warnings (#27)\n - [changed] Explicitly list supported versions in README\n - [deprecated] Python 2 support will be dropped in version 0.4\n\n## [0.3.0] - 2018-03-03\n\n - [changed] Do not apply filter to nested serializers (#14)\n\n## [0.2.0] - 2017-04-07\n\n - [added] Add `omit` option to exclude fields (#11)\n - [fixed] Make it work properly with nested serializers (#8, #10)\n\n## [0.1.1] - 2016-10-16\n\n - [fixed] Make it work in an unit test environment (#2)\n\n## [0.1.0] - 2016-09-30\n\n - Initial release\n\n[Unreleased]: https://github.com/dbrgn/drf-dynamic-fields/compare/v0.4.0...HEAD\n[0.3.1]: https://github.com/dbrgn/drf-dynamic-fields/compare/v0.3.1...v0.4.0\n[0.3.1]: https://github.com/dbrgn/drf-dynamic-fields/compare/v0.3.0...v0.3.1\n[0.3.0]: https://github.com/dbrgn/drf-dynamic-fields/compare/v0.2.0...v0.3.0\n[0.2.0]: https://github.com/dbrgn/drf-dynamic-fields/compare/v0.1.1...v0.2.0\n[0.1.1]: https://github.com/dbrgn/drf-dynamic-fields/compare/v0.1.0...v0.1.1\n[0.1.0]: https://github.com/dbrgn/drf-dynamic-fields/releases/tag/v0.1.0\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014--2016 Danilo Bargen and contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.rst",
    "content": "Dynamic Serializer Fields for Django REST Framework\n===================================================\n\n.. image:: https://secure.travis-ci.org/dbrgn/drf-dynamic-fields.png?branch=master\n    :alt: Build status\n    :target: http://travis-ci.org/dbrgn/drf-dynamic-fields\n\n.. image:: https://img.shields.io/pypi/v/drf-dynamic-fields.svg\n    :alt: PyPI Version\n    :target: https://pypi.python.org/pypi/drf-dynamic-fields\n\n.. image:: https://img.shields.io/pypi/dm/drf-dynamic-fields.svg?maxAge=3600\n    :alt: PyPI Downloads\n    :target: https://pypi.python.org/pypi/drf-dynamic-fields\n\n.. image:: https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000\n    :alt: License is MIT\n    :target: https://github.com/dbrgn/drf-dynamic-fields/blob/master/LICENSE\n\nThis package provides a mixin that allows the user to dynamically select only a\nsubset of fields per resource.\n\nOfficial version support:\n\n- Django 2.2 LTS, 3.2 LTS, 4.0\n- Supported REST Framework versions: 3.8, 3.9\n- Python 3.7+\n\n\nScope\n-----\n\nThis library is about filtering fields based on individual requests. It is\ndeliberately kept simple and we do not plan to add new features (including\nsupport for nested fields). Feel free to contribute improvements, code\nsimplifications and bugfixes though! (See also: `#18\n<https://github.com/dbrgn/drf-dynamic-fields/issues/18>`__)\n\nIf you need more advanced filtering features, maybe `drf-flex-fields\n<https://github.com/rsinger86/drf-flex-fields>`_ could be something for you.\n\n\nInstalling\n----------\n\n::\n\n    pip install drf-dynamic-fields\n\nWhat It Does\n------------\n\nExample serializer:\n\n.. sourcecode:: python\n\n    class IdentitySerializer(DynamicFieldsMixin, serializers.HyperlinkedModelSerializer):\n        class Meta:\n            model = models.Identity\n            fields = ('id', 'url', 'type', 'data')\n\nA regular request returns all fields:\n\n``GET /identities``\n\n.. sourcecode:: json\n\n    [\n      {\n        \"id\": 1,\n        \"url\": \"http://localhost:8000/api/identities/1/\",\n        \"type\": 5,\n        \"data\": \"John Doe\"\n      },\n      ...\n    ]\n\nA query with the `fields` parameter on the other hand returns only a subset of\nthe fields:\n\n``GET /identities/?fields=id,data``\n\n.. sourcecode:: json\n\n    [\n      {\n        \"id\": 1,\n        \"data\": \"John Doe\"\n      },\n      ...\n    ]\n\nAnd a query with the `omit` parameter excludes specified fields.\n\n``GET /identities/?omit=data``\n\n.. sourcecode:: json\n\n    [\n      {\n        \"id\": 1,\n        \"url\": \"http://localhost:8000/api/identities/1/\",\n        \"type\": 5\n      },\n      ...\n    ]\n\nYou can use both `fields` and `omit` in the same request!\n\n``GET /identities/?omit=data,fields=data,id``\n\n.. sourcecode:: json\n\n    [\n      {\n        \"id\": 1\n      },\n      ...\n    ]\n\n\nThough why you would want to do something like that is beyond this author.\n\nIt also works on single objects!\n\n``GET /identities/1/?fields=id,data``\n\n.. sourcecode:: json\n\n    {\n      \"id\": 1,\n      \"data\": \"John Doe\"\n    }\n\nUsage\n-----\n\nWhen defining a serializer, use the ``DynamicFieldsMixin``:\n\n.. sourcecode:: python\n\n    from drf_dynamic_fields import DynamicFieldsMixin\n\n    class IdentitySerializer(DynamicFieldsMixin, serializers.ModelSerializer):\n        class Meta:\n            model = models.Identity\n            fields = ('id', 'url', 'type', 'data')\n\nThe mixin needs access to the ``request`` object. Some DRF classes like the\n``ModelViewSet`` set that by default, but if you handle serializers yourself,\npass in the request through the context:\n\n.. sourcecode:: python\n\n    events = Event.objects.all()\n    serializer = EventSerializer(events, many=True, context={'request': request})\n\n\nWarnings\n--------\n\nIf the request context does not have access to the request, a warning is\nemitted::\n\n   UserWarning: Context does not have access to request.\n\nFirst, make sure that you are passing the request to the serializer context (see\n\"Usage\" section).\n\nThere are some cases (e.g. nested serializers) where you cannot get rid of the\nwarning that way (see `issue 27 <https://github.com/dbrgn/drf-dynamic-fields/issues/27>`_).\nIn that case, you can silence the warning through ``settings.py``:\n\n.. sourcecode:: python\n\n   DRF_DYNAMIC_FIELDS = {\n      'SUPPRESS_CONTEXT_WARNING': True,\n   }\n\n\nTesting\n-------\n\nTo run tests, install Django and DRF and then run ``runtests.py``:\n\n    $ python runtests.py\n\n\nCredits\n-------\n\n- The implementation is based on `this\n  <http://stackoverflow.com/a/23674297/284318>`__ StackOverflow answer. Thanks\n  ``YAtOff``!\n- The GitHub users ``X17`` and ``rawbeans`` provided improvements on `my gist\n  <https://gist.github.com/dbrgn/4e6fc1fe5922598592d6>`__ that were incorporated\n  into this library. Thanks!\n- For other contributors, please see `Github contributor stats\n  <https://github.com/dbrgn/drf-dynamic-fields/graphs/contributors>`__.\n\n\nLicense\n-------\n\nMIT license, see ``LICENSE`` file.\n"
  },
  {
    "path": "RELEASING.md",
    "content": "# Release process\n\nSigning key: https://dbrgn.ch/F2F3A5FA.asc\n\nUsed variables:\n\n    export VERSION={VERSION}\n    export GPG=F2F3A5FA\n\nUpdate version number in setup.py and CHANGELOG.md:\n\n    vim -p setup.py CHANGELOG.md\n\nDo a signed commit and signed tag of the release:\n\n    git add setup.py CHANGELOG.md\n    git commit -S${GPG} -m \"Release v${VERSION}\"\n    git tag -u ${GPG} -m \"Release v${VERSION}\" v${VERSION}\n\nBuild source and binary distributions:\n\n    python3 setup.py sdist\n    python3 setup.py bdist_wheel\n\nSign files:\n\n    gpg --detach-sign -u ${GPG} -a dist/drf_dynamic_fields-${VERSION}.tar.gz\n    gpg --detach-sign -u ${GPG} -a dist/drf_dynamic_fields-${VERSION}-py2.py3-none-any.whl\n\nUpload package to PyPI:\n\n    twine3 upload dist/drf_dynamic_fields-${VERSION}*\n    git push\n    git push --tags\n"
  },
  {
    "path": "drf_dynamic_fields/__init__.py",
    "content": "\"\"\"\nMixin to dynamically select only a subset of fields per DRF resource.\n\"\"\"\nimport warnings\n\nfrom django.conf import settings\nfrom django.utils.functional import cached_property\n\n\nclass DynamicFieldsMixin(object):\n    \"\"\"\n    A serializer mixin that takes an additional `fields` argument that controls\n    which fields should be displayed.\n    \"\"\"\n\n    @cached_property\n    def fields(self):\n        \"\"\"\n        Filters the fields according to the `fields` query parameter.\n\n        A blank `fields` parameter (?fields) will remove all fields. Not\n        passing `fields` will pass all fields individual fields are comma\n        separated (?fields=id,name,url,email).\n\n        \"\"\"\n        fields = super(DynamicFieldsMixin, self).fields\n\n        if not hasattr(self, \"_context\"):\n            # We are being called before a request cycle\n            return fields\n\n        # Only filter if this is the root serializer, or if the parent is the\n        # root serializer with many=True\n        is_root = self.root == self\n        parent_is_list_root = self.parent == self.root and getattr(\n            self.parent, \"many\", False\n        )\n        if not (is_root or parent_is_list_root):\n            return fields\n\n        try:\n            request = self.context[\"request\"]\n        except KeyError:\n            conf = getattr(settings, \"DRF_DYNAMIC_FIELDS\", {})\n            if not conf.get(\"SUPPRESS_CONTEXT_WARNING\", False) is True:\n                warnings.warn(\n                    \"Context does not have access to request. \"\n                    \"See README for more information.\"\n                )\n            return fields\n\n        # NOTE: drf test framework builds a request object where the query\n        # parameters are found under the GET attribute.\n        params = getattr(request, \"query_params\", getattr(request, \"GET\", None))\n        if params is None:\n            warnings.warn(\"Request object does not contain query parameters\")\n\n        try:\n            filter_fields = params.get(\"fields\", None).split(\",\")\n        except AttributeError:\n            filter_fields = None\n\n        try:\n            omit_fields = params.get(\"omit\", None).split(\",\")\n        except AttributeError:\n            omit_fields = []\n\n        # Drop any fields that are not specified in the `fields` argument.\n        existing = set(fields.keys())\n        if filter_fields is None:\n            # no fields param given, don't filter.\n            allowed = existing\n        else:\n            allowed = set(filter(None, filter_fields))\n\n        # omit fields in the `omit` argument.\n        omitted = set(filter(None, omit_fields))\n\n        for field in existing:\n\n            if field not in allowed:\n                fields.pop(field, None)\n\n            if field in omitted:\n                fields.pop(field, None)\n\n        return fields\n"
  },
  {
    "path": "manage.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nDjango manage.py, only used for testing.\n\"\"\"\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"tests.settings\")\n    from django.core.management import execute_from_command_line\n\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "runtests.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8\n\"\"\"\nRun tests with python runtests.py\n\nTaken from the django cookiecutter project.\n\"\"\"\nimport os\nimport sys\n\nimport django\nfrom django.conf import settings\nfrom django.test.utils import get_runner\n\n\ndef run_tests(*test_args):\n    if not test_args:\n        test_args = [\"tests\"]\n\n    os.environ[\"DJANGO_SETTINGS_MODULE\"] = \"tests.settings\"\n    django.setup()\n    test_runner = get_runner(settings)()\n    failures = test_runner.run_tests(test_args)\n    sys.exit(bool(failures))\n\n\nif __name__ == \"__main__\":\n    run_tests(*sys.argv[1:])\n"
  },
  {
    "path": "setup.cfg",
    "content": "[bdist_wheel]\nuniversal=1\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\n\nreadme = open('README.rst').read()\n\nsetup(name='drf_dynamic_fields',\n      version='0.4.0',\n      description='Dynamically return subset of Django REST Framework serializer fields',\n      author='Danilo Bargen',\n      author_email='mail@dbrgn.ch',\n      url='https://github.com/dbrgn/drf-dynamic-fields',\n      packages=['drf_dynamic_fields'],\n      zip_safe=True,\n      include_package_data=True,\n      license='MIT',\n      keywords='drf restframework rest_framework django_rest_framework serializers',\n      long_description=readme,\n      classifiers=[\n          'Development Status :: 5 - Production/Stable',\n          'License :: OSI Approved :: MIT License',\n          'Programming Language :: Python :: 3',\n          'Framework :: Django',\n          'Environment :: Web Environment',\n      ],\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/models.py",
    "content": "\"\"\"\nSome models for the tests. We are modelling a school.\n\"\"\"\nfrom django.db import models\n\n\nclass Teacher(models.Model):\n    name = models.CharField(max_length=30)\n    age = models.IntegerField()\n\n\nclass School(models.Model):\n    \"\"\"Schools just have teachers, no students.\"\"\"\n\n    name = models.CharField(max_length=30)\n    teachers = models.ManyToManyField(Teacher)\n"
  },
  {
    "path": "tests/serializers.py",
    "content": "\"\"\"\nFor the tests.\n\"\"\"\nfrom rest_framework import serializers\n\nfrom drf_dynamic_fields import DynamicFieldsMixin\n\nfrom .models import Teacher, School\n\n\nclass TeacherSerializer(DynamicFieldsMixin, serializers.ModelSerializer):\n    \"\"\"\n    The request_info field is to highlight the issue accessing request during\n    a nested serializer.\n    \"\"\"\n\n    request_info = serializers.SerializerMethodField()\n\n    class Meta:\n        model = Teacher\n        fields = (\"id\", \"request_info\", \"age\", \"name\")\n\n    def get_request_info(self, teacher):\n        \"\"\"\n        a meaningless method that attempts\n        to access the request object.\n        \"\"\"\n        request = self.context[\"request\"]\n        return request.build_absolute_uri(\"/api/v1/teacher/{}\".format(teacher.pk))\n\n\nclass SchoolSerializer(DynamicFieldsMixin, serializers.ModelSerializer):\n    \"\"\"\n    Interesting enough serializer because the TeacherSerializer\n    will use ListSerializer due to the `many=True`\n    \"\"\"\n\n    teachers = TeacherSerializer(many=True, read_only=True)\n\n    class Meta:\n        model = School\n        fields = (\"id\", \"teachers\", \"name\")\n"
  },
  {
    "path": "tests/settings.py",
    "content": "# -*- coding: utf-8\n\"\"\"\nSettings for test.\n\"\"\"\nimport django\n\nDEBUG = True\nUSE_TZ = True\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = \"**************************************************\"\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": \":memory:\",\n    }\n}\n\nROOT_URLCONF = \"tests.urls\"\n\nINSTALLED_APPS = [\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sites\",\n    \"drf_dynamic_fields\",\n    \"tests\",\n]\n\nSITE_ID = 1\n\nMIDDLEWARE = ()\n"
  },
  {
    "path": "tests/test_mixins.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\ntest_drf-dynamic-fields\n-----------\n\nTests for `drf-dynamic-fields` mixins\n\"\"\"\nfrom collections import OrderedDict\n\nfrom django.test import TestCase, RequestFactory\n\nfrom .serializers import SchoolSerializer, TeacherSerializer\nfrom .models import Teacher, School\n\n\nclass TestDynamicFieldsMixin(TestCase):\n    \"\"\"\n    Test case for the DynamicFieldsMixin\n    \"\"\"\n\n    def test_removes_fields(self):\n        \"\"\"\n        Does it actually remove fields?\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?fields=id\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(set(serializer.fields.keys()), set((\"id\",)))\n\n    def test_fields_left_alone(self):\n        \"\"\"\n        What if no fields param is passed? It should not touch the fields.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(\n            set(serializer.fields.keys()), set((\"id\", \"request_info\", \"age\", \"name\"))\n        )\n\n    def test_fields_all_gone(self):\n        \"\"\"\n        If we pass a blank fields list, then no fields should return.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?fields\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(set(serializer.fields.keys()), set())\n\n    def test_ordinary_serializer(self):\n        \"\"\"\n        Check the full JSON output of the serializer.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?fields=id,age\")\n        teacher = Teacher.objects.create(name=\"Susan\", age=34)\n\n        serializer = TeacherSerializer(teacher, context={\"request\": request})\n\n        self.assertEqual(serializer.data, {\"id\": teacher.id, \"age\": teacher.age})\n\n    def test_omit(self):\n        \"\"\"\n        Check a basic usage of omit.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?omit=request_info\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(set(serializer.fields.keys()), set((\"id\", \"name\", \"age\")))\n\n    def test_omit_and_fields_used(self):\n        \"\"\"\n        Can they be used together.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?fields=id,request_info&omit=request_info\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(set(serializer.fields.keys()), set((\"id\",)))\n\n    def test_omit_everything(self):\n        \"\"\"\n        Can remove it all tediously.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?omit=id,request_info,age,name\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(set(serializer.fields.keys()), set())\n\n    def test_omit_nothing(self):\n        \"\"\"\n        Blank omit doesn't affect anything.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?omit\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(\n            set(serializer.fields.keys()), set((\"id\", \"request_info\", \"name\", \"age\"))\n        )\n\n    def test_omit_non_existant_field(self):\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?omit=pretend\")\n        serializer = TeacherSerializer(context={\"request\": request})\n\n        self.assertEqual(\n            set(serializer.fields.keys()), set((\"id\", \"request_info\", \"name\", \"age\"))\n        )\n\n    def test_as_nested_serializer(self):\n        \"\"\"\n        Nested serializers are not filtered.\n        \"\"\"\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?fields=teachers\")\n\n        school = School.objects.create(name=\"Python Heights High\")\n        teachers = [\n            Teacher.objects.create(name=\"Shane\", age=45),\n            Teacher.objects.create(name=\"Kaz\", age=29),\n        ]\n        school.teachers.add(*teachers)\n\n        serializer = SchoolSerializer(school, context={\"request\": request})\n\n        request_info = \"http://testserver/api/v1/teacher/{}\"\n\n        self.assertEqual(\n            serializer.data,\n            {\n                \"teachers\": [\n                    OrderedDict(\n                        [\n                            (\"id\", teachers[0].id),\n                            (\"request_info\", request_info.format(teachers[0].id)),\n                            (\"age\", teachers[0].age),\n                            (\"name\", teachers[0].name),\n                        ]\n                    ),\n                    OrderedDict(\n                        [\n                            (\"id\", teachers[1].id),\n                            (\"request_info\", request_info.format(teachers[1].id)),\n                            (\"age\", teachers[1].age),\n                            (\"name\", teachers[1].name),\n                        ]\n                    ),\n                ],\n            },\n        )\n\n    def test_serializer_reuse_with_changing_request(self):\n        \"\"\"\n        `fields` is a cached property. Changing the request on an already\n        instantiated serializer will not result in a changed fields attribute.\n\n        This was a deliberate choice we have made in favor of speeding up\n        access to the slow `fields` attribute.\n        \"\"\"\n\n        rf = RequestFactory()\n        request = rf.get(\"/api/v1/schools/1/?fields=id\")\n        serializer = TeacherSerializer(context={\"request\": request})\n        self.assertEqual(set(serializer.fields.keys()), {\"id\"})\n\n        # now change the request on this instantiated serializer.\n        request2 = rf.get(\"/api/v1/schools/1/?fields=id,name\")\n        serializer.context[\"request\"] = request2\n        self.assertEqual(set(serializer.fields.keys()), {\"id\"})\n"
  },
  {
    "path": "tests/test_requests.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\ntest_drf-dynamic-fields\n------------\n\nTest for the full request cycle using dynamic fields mixns\n\"\"\"\nfrom collections import OrderedDict\n\nfrom django.test import TestCase, RequestFactory\n\nfrom rest_framework.reverse import reverse\n\nfrom .serializers import SchoolSerializer, TeacherSerializer\nfrom .models import Teacher, School\n\n\nclass TestDynamicFieldsViews(TestCase):\n    \"\"\"\n    Testing using dynamic fields in request framework views.\n    \"\"\"\n\n    def setUp(self):\n        \"\"\"\n        Create some teachers and schools.\n        \"\"\"\n        teachers = [(\"Craig\", 34), (\"Kaz\", 29), (\"Sun\", 62)]\n\n        schools = [\"Python Heights High\", \"Ruby Consolidated\", \"Java Coffee School\"]\n\n        t = [Teacher.objects.create(name=name, age=age) for name, age in teachers]\n\n        for name in schools:\n            s = School.objects.create(name=name)\n            s.teachers.add(*t)\n\n    def test_teacher_basic(self):\n\n        response = self.client.get(reverse(\"teacher-list\"))\n        for teacher in response.data:\n            self.assertEqual(teacher.keys(), {\"id\", \"request_info\", \"age\", \"name\"})\n\n    def test_teacher_fields(self):\n\n        response = self.client.get(reverse(\"teacher-list\"), {\"fields\": \"id,age\"})\n        for teacher in response.data:\n            self.assertEqual(teacher.keys(), {\"id\", \"age\"})\n\n    def test_teacher_omit(self):\n\n        response = self.client.get(reverse(\"teacher-list\"), {\"omit\": \"id,age\"})\n        for teacher in response.data:\n            self.assertEqual(teacher.keys(), {\"request_info\", \"name\"})\n\n    def test_nested_teacher_fields(self):\n\n        response = self.client.get(reverse(\"school-list\"), {\"fields\": \"name,teachers\"})\n        for school in response.data:\n            self.assertEqual(school.keys(), {\"teachers\", \"name\"})\n            self.assertEqual(\n                school[\"teachers\"][0].keys(), {\"id\", \"request_info\", \"age\", \"name\"}\n            )\n"
  },
  {
    "path": "tests/urls.py",
    "content": "# -*- coding: utf-8\nfrom rest_framework import routers\n\nfrom .views import SchoolViewSet, TeacherViewSet\n\nrouter = routers.SimpleRouter()\nrouter.register(\"teachers\", TeacherViewSet)\nrouter.register(\"schools\", SchoolViewSet)\n\nurlpatterns = router.urls\n"
  },
  {
    "path": "tests/views.py",
    "content": "from rest_framework.viewsets import ModelViewSet\n\nfrom .models import School, Teacher\nfrom .serializers import SchoolSerializer, TeacherSerializer\n\n\nclass TeacherViewSet(ModelViewSet):\n    queryset = Teacher.objects.all()\n    serializer_class = TeacherSerializer\n\n\nclass SchoolViewSet(ModelViewSet):\n    queryset = School.objects.all()\n    serializer_class = SchoolSerializer\n"
  }
]